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
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'])
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.
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.
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
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 ?