how get jwt token from IAP in Flask? - python

I have a simple Flask application on a Google App Engine, protected by Identity-Aware Proxy.
The authentication works well but I only recover the GCP_IAP_UID cookie, when I want to recover the JWT found in GCP_IAAP_AUTH_TOKEN_XXXXX.
I have tried
google.auht jwt
Flask request cookies
Requests
None of this module retrieve the token.
The browser shows the cookies I need (show image linked below), but Flask can't catch them.
Any idea is welcome
I try with the google.auth jwt, but it's empty
I try with Flask request.cookies but I get only on cookie, the UID (see code)
I try with requests.cookies.RequestsCookieJar (last try) but there is no cookie
My apps run with python 37 and here are the requirements:
Flask==1.1.2
Flask-SSLify==0.1.5
Werkzeug==1.0.1
google-api-python-client==1.6.0
google-cloud-storage==1.6.0
gunicorn==19.10.0
oauth2client==4.1.3
six==1.14.0
requests_toolbelt==0.9.1
google-auth-httplib2==0.0.3
ez-setup==0.9
Below the code of the init.py where I want to validate the jwt.
import logging
from flask import Flask, redirect, url_for, request
from google.auth import jwt
import requests
user_email = ""
nickname = ""
jwtr = ""
try:
import googleclouddebugger
googleclouddebugger.enable()
except ImportError:
pass
def create_app(config, debug=False, testing=True, config_overrides=None):
app = Flask(__name__)
app.config.from_object(config)
app.debug = debug
app.testing = testing
if config_overrides:
app.config.update(config_overrides)
# Configure logging
# if not app.testing:
logging.basicConfig(level=logging.INFO)
# Register the Bookshelf CRUD blueprint.
from .crud import crud
app.register_blueprint(crud, url_prefix='/app')
# Add a default root route.
#app.route("/")
def index():
jwtr = ""
# Goto see the log below
logging.info("1 nb cookies={}".format(len(request.cookies)))
logging.info("GCP_IAP_UID={}".format(request.cookies.get('GCP_IAP_UID')))
jar = requests.cookies.RequestsCookieJar()
logging.info("2 nb cookies={}".format(len(jar)))
for cle in jar.keys():
if cle.startswith('GCP_IAAP_AUTH_TOKEN_'):
jwtr = jar.get(cle)
logging.info("jwtr={}".format(jwtr))
try:
user_id, user_email, error_str = validate_iap_jwt_from_app_engine(jwtr,
'123456789012', 'xxxxx-yyyy')
if user_email is not None:
nickname = user_email.split('#')[0]
logging.info("nickmane="+nickname + " user_id="+user_id + " user_email=" +
user_email)
return redirect(url_for('crud.index'))
else:
return ""
except (ValueError, requests.exceptions.RequestException) as e:
logging.error("C'est moche !!{}!!".format(e))
return ""
Last but least a log file:
INFO:root:1 nb cookies=1
INFO:root:GCP_IAP_UID=10944565464656564
INFO:root:2 nb cookies=0
ERROR:root:**ERROR: JWT validation error Wrong number of segments in token: b''**
Cookies at browser level

in fact the jwt token can be found in the header like this:
AUTHORIZATION_HEADER = 'X-Goog-Iap-Jwt-Assertion'
if request.headers.get(AUTHORIZATION_HEADER):
jwtr = request.headers.get(AUTHORIZATION_HEADER)
logging.info("header authz={}".format(jwtr))
You can found all you need in https://cloud.google.com/iap/docs/signed-headers-howto

Related

Unable to fetch OAuth2.0 Token from Azure-DevOps , using Python

I am trying to use OAuth2 to access the Azure DevopsAPI, to query work-items.
But I am unable to get the access tokene.
I am using Python and Flask. My approach is based on these resources:
Microsoft documentation , there currently Step 3 is relevant
OAuth Tutorial, which worked fine for Github, but is not working for Azure.
Relevant libraries:
from requests_oauthlib import OAuth2Session
from flask import Flask, request, redirect, session, url_for
Parameters:
client_id = "..."
client_secret = "..."
authorization_base_url = "https://app.vssps.visualstudio.com/oauth2/authorize"
token_url = "https://app.vssps.visualstudio.com/oauth2/token"
callback_url = "..."
Step 1: User Authorization. (works fine)
#app.route("/")
def demo():
azure = OAuth2Session(client_id)
authorization_url, state = azure.authorization_url(authorization_base_url)
session['oauth_state'] = state
authorization_url += "&scope=" + authorized_scopes + "&redirect_uri=" + callback_url
print(authorization_url)
return redirect(authorization_url)
Step 2: Retrieving an access token (generates an error)
#app.route("/callback", methods=["GET"])
def callback():
fetch_body = "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
"&client_assertion=" + client_secret + \
"&grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
"&assertion=" + request.args["code"] + \
"&redirect_uri=" + callback_url
azure = OAuth2Session(client_id, state=session['oauth_state'])
token = azure.fetch_token(token_url=token_url, client_secret=client_secret,
body=fetch_body,
authorization_response=request.url)
azure.request()
session['oauth_token'] = token
return redirect(url_for('.profile'))
The application-registration and adhoc-SSL-certification are working fine (using it just temporary).
When I use the client_assertion in Postman, I get a correct response from Azure:
But when I execute the code, this error is thrown:
oauthlib.oauth2.rfc6749.errors.MissingTokenError: (missing_token) Missing access token parameter.
Which only lets me know, that no token was received.
There is one issue in the generated request body, where the grant_type is added twice:
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
grant_type=authorization_code
The first value is expected by Azure, but the second one is generated automatically by the library.
Now when I specify the grant_type in the fetch_token call, like this:
token = azure.fetch_token(token_url=token_url, client_secret=client_secret,
body=fetch_body, grant_type="urn:ietf:params:oauth:grant-type:jwt-bearer",
authorization_response=request.url)
I get this error
TypeError: prepare_token_request() got multiple values for argument 'grant_type'
And the actual request to Azure is not even sent.
I see in the web_application.py that is used by oauth2_session.py, that grant_type ='authorization_code' is set fixed, so I guess this library is generally incompatible with Azure.
Is that the case?
If so, what would be the simplest way to connect to Azure-OAuth with Python (Flask)?
I would be very grateful for any advice and help that point me in the right direction.
I just found the azure.devops library that solves my problem.
Ressources
https://github.com/Microsoft/azure-devops-python-api
https://github.com/microsoft/azure-devops-python-samples/blob/main/src/samples/work_item_tracking.py
azure-devops-python-api query for work item where field == string
from azure.devops.connection import Connection
from azure.devops.v5_1.work_item_tracking import Wiql
from msrest.authentication import BasicAuthentication
import pprint
# Fill in with your personal access token and org URL
personal_access_token = '... PAT'
organization_url = 'https://dev.azure.com/....'
# Create a connection to the org
credentials = BasicAuthentication('', personal_access_token)
connection = Connection(base_url=organization_url, creds=credentials)
# Get a client (the "core" client provides access to projects, teams, etc)
core_client = connection.clients.get_core_client()
wit_client = connection.clients.get_work_item_tracking_client()
query = "SELECT [System.Id], [System.WorkItemType], [System.Title], [System.AssignedTo], [System.State]," \
"[System.Tags] FROM workitems WHERE [System.TeamProject] = 'Test'"
wiql = Wiql(query=query)
query_results = wit_client.query_by_wiql(wiql).work_items
for item in query_results:
work_item = wit_client.get_work_item(item.id)
pprint.pprint(work_item.fields['System.Title'])

403 IAM permission 'dialogflow.sessions.detectIntent' error in flask

I am trying to use Dialogflow in my Flask app but I am getting 403 IAM permission 'dialogflow.sessions.detectIntent' I have checked twice my role is project owner in cloud console. I am following this tutorial.
This is my app.py
from flask import Flask
from flask import request, jsonify
import os
import dialogflow
from google.api_core.exceptions import InvalidArgument
import requests
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = 'service_key.json'
DIALOGFLOW_PROJECT_ID = 'whatsappbotagent-gmsl'
DIALOGFLOW_LANGUAGE_CODE = 'en'
SESSION_ID = 'me'
app = Flask(__name__)
app.config["DEBUG"] = True
#app.route('/')
def root():
return "Hello World"
#app.route('/api/getMessage', methods = ['POST'])
def home():
message = request.form.get('Body')
mobnu = request.form.get('From')
session_client = dialogflow.SessionsClient()
session = session_client.session_path(DIALOGFLOW_PROJECT_ID, SESSION_ID)
text_input = dialogflow.types.TextInput(text = message, language_code = DIALOGFLOW_LANGUAGE_CODE)
query_input = dialogflow.types.QueryInput(text = text_input)
try:
response = session_client.detect_intent(session = session, query_input = query_input)
except InvalidArgument:
raise
print("Query text: ", response.query_result.query_text)
# sendMessage()
return response.query_result.fullfilment_text
if __name__ == '__main__':
app.run()
OK, so after a lot of searching and trial and error I found the issue.
Actually while creating a service account the user gets a service email and that email will have access to use the project but I was not adding that email in project permission emails. Adding that email in IAM & Admin > IAM > +ADD will make it work.
Maybe there was a better solution but this is what works for me and hopefully solves others problem if have similar issue like this.

Flask Session drops for MS Graph Authentication

I'm running a simple flask app in a Kubernettes pod using the basic login sample provided by MS. Locally it works great. But once deployed the session[''state'] variable seems to get dropped preventing the login from completing. Below is my config:
CLIENT_ID = os.getenv('AZURE_CLIENT_ID')
CLIENT_SECRET = os.getenv('AZURE_CLIENT_SECRET')
# production redriect uri
REDIRECT_URI = 'https://agent-workstation-service-manager-
development.app.k8s.nonprod.aws.lmig.com/login/authorized'
# local dev redirect uri
# REDIRECT_URI = 'http://localhost:5000/login/authorized'
AUTHORITY_URL = 'https://login.microsoftonline.com/my_companies_code'
AUTH_ENDPOINT = '/oauth2/v2.0/authorize'
TOKEN_ENDPOINT = '/oauth2/v2.0/token'
RESOURCE = 'https://graph.microsoft.com/'
API_VERSION = 'v1.0'
SCOPES = ['User.Read']
then the code to call it
#app.route('/login')
def login():
"""Prompt user to authenticate."""
session['state'] = str(uuid.uuid4())
return MSGRAPH.authorize(callback=azure.REDIRECT_URI, state=session['state'])
#app.route('/login/authorized')
def authorized():
"""Handler for the application's Redirect Uri."""
if str(session['state']) != str(request.args['state']):
raise Exception('state returned to redirect URL does not match!')
response = MSGRAPH.authorized_response()
session['access_token'] = response['access_token']
return redirect('/graphcall')
my reply_URLs from the azure client set up point to both locations, but why does it drop the session variable when it gets deployed?
Keyerror: 'state' on the line that starts with: if str(session['state'].....
my req.txt file:
Flask==1.1.1
flask-wtf==0.14.2
ldap3==2.6.1
pyodbc==4.0.28
flake8==3.7.9
pytest
WMI==1.4.9
requests==2.22.0
werkzeug==0.16.0
pyyaml==5.2
python-dateutil==2.8.1
oauthlib==2.0.7
Flask-OAuthlib==0.9.5
gunicorn==20.0.4
Session affinity is the answer to this. Reduced to one pod and it works great.

MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response

This is driving me absolutely crazy and preventing me from being able to do local dev/test.
I have a flask app that uses authlib (client capabilities only). When a user hits my home page, my flask backend redirects them to /login which in turn redirects to Google Auth. Google Auth then posts them back to my app's /auth endpoint.
For months, I have been experiencing ad-hoc issues with authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response. It feels like a cookie problem and most of the time, I just open a new browser window or incognito or try to clear cache and eventually, it sort of works.
However, I am now running the exact same application inside of a docker container and at one stage this was working. I have no idea what I have changed but whenever I browse to localhost/ or 127.0.0.1/ and go through the auth process (clearing cookies each time to ensure i'm not auto-logged in), I am constantly redirected back to localhost/auth?state=blah blah blah and I experience this issue:
authlib.integrations.base_client.errors.MismatchingStateError: mismatching_state: CSRF Warning! State not equal in request and response.
I think the relevant part of my code is:
#app.route("/", defaults={"path": ""})
#app.route("/<path:path>")
def catch_all(path: str) -> Union[flask.Response, werkzeug.Response]:
if flask.session.get("user"):
return app.send_static_file("index.html")
return flask.redirect("/login")
#app.route("/auth")
def auth() -> Union[Tuple[str, int], werkzeug.Response]:
token = oauth.google.authorize_access_token()
user = oauth.google.parse_id_token(token)
flask.session["user"] = user
return flask.redirect("/")
#app.route("/login")
def login() -> werkzeug.Response:
return oauth.google.authorize_redirect(flask.url_for("auth", _external=True))
I would hugely appreciate any help.
When I run locally, I start with:
export FLASK_APP=foo && flask run
When I run inside docker container, i start with:
.venv/bin/gunicorn -b :8080 --workers 16 foo
Issue was that SECRET_KEY was being populated using os.random which yielded different values for different workers and thus, couldn't access the session cookie.
#adamcunnington here is how you can debug it:
#app.route("/auth")
def auth() -> Union[Tuple[str, int], werkzeug.Response]:
# Check these two values
print(flask.request.args.get('state'), flask.session.get('_google_authlib_state_'))
token = oauth.google.authorize_access_token()
user = oauth.google.parse_id_token(token)
flask.session["user"] = user
return flask.redirect("/")
Check the values in request.args and session to see what's going on.
Maybe it is because Flask session not persistent across requests in Flask app with Gunicorn on Heroku
How I Fix My Issue
install old version of authlib it work fine with fastapi and flask
Authlib==0.14.3
For Fastapi
uvicorn==0.11.8
starlette==0.13.6
Authlib==0.14.3
fastapi==0.61.1
Imporantt if using local host for Google auth make sure get https certifcate
install chocolatey and setup https check this tutorial
https://dev.to/rajshirolkar/fastapi-over-https-for-development-on-windows-2p7d
ssl_keyfile="./localhost+2-key.pem" ,
ssl_certfile= "./localhost+2.pem"
--- My Code ---
from typing import Optional
from fastapi import FastAPI, Depends, HTTPException
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from starlette.config import Config
from starlette.requests import Request
from starlette.middleware.sessions import SessionMiddleware
from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse
from authlib.integrations.starlette_client import OAuth
# Initialize FastAPI
app = FastAPI(docs_url=None, redoc_url=None)
app.add_middleware(SessionMiddleware, secret_key='!secret')
#app.get('/')
async def home(request: Request):
# Try to get the user
user = request.session.get('user')
if user is not None:
email = user['email']
html = (
f'<pre>Email: {email}</pre><br>'
'documentation<br>'
'logout'
)
return HTMLResponse(html)
# Show the login link
return HTMLResponse('login')
# --- Google OAuth ---
# Initialize our OAuth instance from the client ID and client secret specified in our .env file
config = Config('.env')
oauth = OAuth(config)
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(
name='google',
server_metadata_url=CONF_URL,
client_kwargs={
'scope': 'openid email profile'
}
)
#app.get('/login', tags=['authentication']) # Tag it as "authentication" for our docs
async def login(request: Request):
# Redirect Google OAuth back to our application
redirect_uri = request.url_for('auth')
print(redirect_uri)
return await oauth.google.authorize_redirect(request, redirect_uri)
#app.route('/auth/google')
async def auth(request: Request):
# Perform Google OAuth
token = await oauth.google.authorize_access_token(request)
user = await oauth.google.parse_id_token(request, token)
# Save the user
request.session['user'] = dict(user)
return RedirectResponse(url='/')
#app.get('/logout', tags=['authentication']) # Tag it as "authentication" for our docs
async def logout(request: Request):
# Remove the user
request.session.pop('user', None)
return RedirectResponse(url='/')
# --- Dependencies ---
# Try to get the logged in user
async def get_user(request: Request) -> Optional[dict]:
user = request.session.get('user')
if user is not None:
return user
else:
raise HTTPException(status_code=403, detail='Could not validate credentials.')
return None
# --- Documentation ---
#app.route('/openapi.json')
async def get_open_api_endpoint(request: Request, user: Optional[dict] = Depends(get_user)): # This dependency protects our endpoint!
response = JSONResponse(get_openapi(title='FastAPI', version=1, routes=app.routes))
return response
#app.get('/docs', tags=['documentation']) # Tag it as "documentation" for our docs
async def get_documentation(request: Request, user: Optional[dict] = Depends(get_user)): # This dependency protects our endpoint!
response = get_swagger_ui_html(openapi_url='/openapi.json', title='Documentation')
return response
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, port=8000,
log_level='debug',
ssl_keyfile="./localhost+2-key.pem" ,
ssl_certfile= "./localhost+2.pem"
)
.env file
GOOGLE_CLIENT_ID=""
GOOGLE_CLIENT_SECRET=""
Google Console Setup

Google App Engine Flask Error in 'Access-Control-Allow-Origin' CORPS settings

I am using Google App Engine and flask to get the authentication from the Facebook
I have created the FB Application and it connects fine
I am using the following libraries
flask-cors==2.1.2
flask-restful==0.3.5
I am setting CORS settings as follows
cors = CORS(app, resources={r"/api/v1/*": {"origins": "*"}})
I am getting the following error
XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth?response_type=code&client_id=40400553…alhost%3A8080%2Fapi%2Fv1%2Fusers%2Ffacebook2%2F&scope=email&_external=True. Redirect from 'https://www.facebook.com/dialog/oauth?response_type=code&client_id=40400553…alhost%3A8080%2Fapi%2Fv1%2Fusers%2Ffacebook2%2F&scope=email&_external=True' to 'https://www.facebook.com/login.php?skip_api_login=1&api_key=404005532980775…_&display=page&locale=en_GB&logger_id=c25bd044-3438-4a9b-b9ef-3f1701bf1d1c' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
Request Handler
SECRET_KEY = 'development key'
DEBUG = True
FACEBOOK_APP_ID = '40400553-----'
FACEBOOK_APP_SECRET = 'bc04c3c33f-----'
facebook_config = dict(
access_token_url='/oauth/access_token',
authorize_url='/oauth/authorize',
base_url='https://graph.facebook.com/',
consumer_key=FACEBOOK_APP_ID,
consumer_secret=FACEBOOK_APP_SECRET,
request_token_params={'scope': 'email'},
)
def create_oauth_app(service_config, name):
upper_name = name.upper()
app.config[upper_name] = service_config
service_oauth = oauth.OAuth()
service_app = service_oauth.remote_app(name, app_key=upper_name)
service_oauth.init_app(app)
return service_app
def signin_fb_oauth(oauthapp, scheme=None):
try:
flask.session.pop('oauth_token', None)
save_request_params()
print '****************************'
print 'signin oauth'
print '*******************************'
#call_back_url_appengine = 'http://localhost:3000/api/v1/users/facebook2/'
call_back_url_flask= 'http://localhost:8080/api/v1/users/facebook2/'
fb_auth = oauthapp.authorize(
callback=call_back_url_flask,
_external=True
)
return fb_auth
except oauth.OAuthException:
flask.flash(
'Something went wrong with sign in. Please try again.',
category='danger',
)
print '*******************************************'
print 'Error in AuthenticationError'
print '*******************************************'
return flask.redirect(flask.url_for('signin', next=util.get_next_url()))
This application has a Angualr2 front end and Flask Google App Engine Backend
What could possibly go wrong here ?

Categories

Resources