The Problem:
The library flask-oidc includes the scope parameter into the authorization-code/access-token exchange request, which unsurprisingly throws the following error:
oauth2client.client.FlowExchangeError: invalid_request Scope parameter is not supported on an authorization code access_token exchange request. Scope parameter should be supplied to the authorized request.
The Question:
Is this a configuration problem or a library problem?
My Configurations:
Flask Application:
app.config.update({
'DEBUG': True,
'TESTING': True,
'SECRET_KEY': 'secret',
'SERVER_NAME' : 'flask.example.com:8000',
'OIDC_COOKIE_SECURE': False,
'OIDC_REQUIRE_VERIFIED_EMAIL': False,
'OIDC_CALLBACK_ROUTE': '/oidc/callback',
'OIDC_CLIENT_SECRETS': 'client_secrets.json'
})
oidc = OpenIDConnect(app)
client_secrets.json
{
"web": {
"auth_uri": "http://openam.example.com:8080/openam/oauth2/realms/root/authorize",
"issuer": "http://openam.example.com:8080/openam/oauth2/realms/root/",
"userinfo_uri": "http://openam.example.com:8080/openam/oauth2/realms/root/userinfo",
"client_id": "MyClientID",
"client_secret": "password",
"redirect_uris": [
"http://flask.example.com:8000/oidc/callback"
],
"token_uri": "http://openam.example.com:8080/openam/oauth2/realms/root/token",
"token_introspection_uri": "http://openam.example.com:8080/openam/oauth2/realms/root/introspect"
}
}
Access Manager
For the access manager I use OpenAM. I configured an OpenAM client agent as follows:
Client ID = MyClientID
Client Secret = password
Response Type = code
Token Endpoint Authentication Method = client_secret_post
Redirect URI = http://flask.example.com:8000/oidc/callback
Context:
I use flask-oidc for the logic on the application side and OpenAM for the identity and access management - both applications run in docker containers. When using simple curl commands I can retrieve an authorization grant as well as an authentication token (grant type: Authorization Code Grant). However, using the mentioned library, after logging in to OpenAM and granting authorization to the application (endpoint 'oauth2/authorize'), flask-oidc sends the following GET request:
GET /oidc/callback?code=<some code> \
&scope=openid%20email \
&iss=http%3A%2F%2Fopenam.example.com%3A8080%2Fopenam%2Foauth2 \
&state=<some state> \
&client_id=MyClientID
Which leads to the error mentioned above.
While this does not directly answer the question, the best answer I could find was to use pyJWT or oauthlib instead of using flask-oidc. I found pyjwt was very straightforward in most respects, and there is an excellent tutorial here:
SSO Using Flask Request Oauthlib and pyjwt
I am not sure of this, but because the error is generated by oauth2client, not flask-oidc, it is possible the error is actually just related to the deprecated oathlib2clientlib.
There was a detailed request to mark the entire flask-oidc project as deprecated, but that request was made several years after the flask-oidc project was stopped being maintained. I hope one day flask will roove this link from their site because it is misleading to think that it is a main part of flask.
Related
I'm trying to implement Google sign in using DRF and dj_rest_auth.
I've set up 'django-allauth' with Google as provider and the sign in process works in the web browser.
I need to connect an android app with my backend. I've created API endpoints which will require authentication.
According to the docs, code is required in order to complete authentication and receive the token.
After doing some research, I found that code required by dj_rest_auth can be obtained by visiting:
https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<YOUR CALLBACK URL>&prompt=consent&response_type=code&client_id=<YOUR CLIENT ID>&scope=openid%20email&access_type=offline
However, even after passing code returned in the query param (after decoding from URL format), the following error is shown:
Error retrieving access token: b'{\n "error": "invalid_grant",\n "error_description": "Bad Request"\n}'
To see if I can log in with a recent access token, I signed in with my Google account from the homepage , copied the access token from the admin section and submitted it to the endpoint http://localhost:8000/dj-rest-auth/google/. I was able to receive the auth token generated by dj_rest_auth.
I need help in getting the auth token by providing code in the post request.
My code:
# urls.py
...
path('dj-rest-auth/', include('dj_rest_auth.urls')),
path('dj-rest-auth/registration/', include('dj_rest_auth.registration.urls')),
path('dj-rest-auth/google/', home.GoogleLogin.as_view(), name='google_login'),
...
# views.py
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
class GoogleLogin(SocialLoginView):
adapter_class = GoogleOAuth2Adapter
callback_url = 'http://localhost:8000/accounts/google/login/callback/'
client_class = OAuth2Client
...
References:
Google Social Authentication with dj-rest-auth #220
Minimal example for SPA implementation of social login #147
Please help me.
Did you create your authentication keys on Google Cloud and add them in settings.py ?
It looks like that for me in settings :
SOCIALACCOUNT_PROVIDERS = {
"google": {
# For each OAuth based provider, either add a ``SocialApp``
# (``socialaccount`` app) containing the required client
# credentials, or list them here:
"APP": {
"client_id": os.environ.get("GOOGLE_CLIENT_ID"),
"secret": os.environ.get("GOOGLE_SECRET_KEY"),
"key": ""
},
# These are provider-specific settings that can only be
# listed here:
"SCOPE": [
"profile",
"email",
],
"AUTH_PARAMS": {
"access_type": "online",
}
}}
I have an API Gateway defined in the python cdk that will accept CURL Restful requests to upload / read / delete files from an S3 bucket:
api = api_gw.RestApi(self, "file-api",
rest_api_name="File REST Service")
file = api.root.add_resource("{id}")
get_files_integration = api_gw.LambdaIntegration(handler,
request_templates={"application/json": '{ "statusCode": "200" }'})
post_file_integration = api_gw.LambdaIntegration(handler)
get_file_integration = api_gw.LambdaIntegration(handler)
delete_file_integration = api_gw.LambdaIntegration(handler)
api.root.add_method("GET", get_files_integration, authorization_type=api_gw.AuthorizationType.COGNITO, authorizer=auth)
file.add_method("POST", post_file_integration); # POST /{id}
file.add_method("GET", get_file_integration); # GET /{id}
file.add_method("DELETE", delete_file_integration); # DELETE /{id}
Is it possible to enable CORS on the API Gateway so that it will perform pre-flight checks and allow external access from a localhost on another machine?
I have attempted to use the existing add_core_preflight() method defined in the documentation I can find but believe this may no longer be valid as of CDK 2.0.
Yes, IResource.add_cors_preflight() does exactly this.
You can also specify default CORS config with the default_cors_preflight_options attribute of RestApi.
Here are the examples from the docs. They're in Typescript, but it will work the same in Python.
The following example will enable CORS for all methods and all origins on all resources of the API:
new apigateway.RestApi(this, 'api', {
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS // this is also the default
}
})
The following example will add an OPTIONS method to the myResource API resource, which only allows GET and PUT HTTP requests from the origin https://amazon.com.
declare const myResource: apigateway.Resource;
myResource.addCorsPreflight({
allowOrigins: [ 'https://amazon.com' ],
allowMethods: [ 'GET', 'PUT' ]
});
Here's my problem:
I have a 365 Family OneDrive subscription with 3 members, my account being the admin.
I am trying to build a python application to read/extract the content of the files I have on this onedrive space based on specific criterias. I want to build it as a command line application, running locally on my PC. I am aware some tools may exist for this but I'd like to code my own solution.
After going through tons of different documentation, I ended up doing the following
Registered my application on the Azure portal
Granted some permission on the Microsoft Graph API (User.read, Files.Read and Files.ReadAll)
Created a secret
Grabbed the sample code provided by Microsoft
Replaces some variables with my Client_Id and Secret
Ran the code
The code returns an access token but the authorization requests fails with 401 - Unauthorized: Access is denied due to invalid credentials.
Here's the Python code I'm using.
import msal
config = {
"authority": "https://login.microsoftonline.com/consumers",
"client_id": "<my client ID>",
"scope": ["https://graph.microsoft.com/.default"],
"secret": "<My secret stuff>",
"endpoint": "https://graph.microsoft.com/v1.0/users"
}
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
)
result = None
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
print("Graph API call result: ")
print(json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
According to the error message, I'm obviously missing something in the authorization process but can't tell what. I'm not even sure about the Authority and Endpoints I should use. My account being a personal one, I have no tenant.
Do I need to set-up / configure some URI somewhere?
Any help would be welcome.
Thank you in advance.
In your client app you need to store the token that you are getting from the MSAL. and then send the token with an authorized request.
For OneDrive, download the OneDrive for python. You can see the different option for Authentication.
The reason you are getting an access token, ID token, and a refresh token is because of the flow you're using. My suggestion is to review the flows for a better understanding of how the authentication process works and what will be returned accordingly. You can use this MSAL library for python.
I've built an App engine API in Python that's fetched by a Node application. The API works as expected for (1) get and post requests in production and (2) get requests in development. It fails on post requests in development and I could use some help figuring out why.
Error messages
In my node environment I see the error:
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:4444' is therefore not allowed
access. The response had HTTP status code 500. If an opaque response
serves your needs, set the request's mode to 'no-cors' to fetch the
resource with CORS disabled.
But I am already using the flask_cors package inside my app so I wonder if this is really a CORS issue.
My activated virtual python environment logs:
File
"/myproject/googleAdsApi/env/lib/python2.7/site-packages/urllib3/contrib/appengine.py",
line 103, in init
"URLFetch is not available in this environment.")
So perhaps I should use an alternative to URLFetch within my virtual environment?
My current implementation
Fetching:
fetch('http://localhost:8080/api/get_accounts', {
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
redirect: "follow",
referrer: "no-referrer",
body: JSON.stringify(credentials)
})
.then(response => response.json())
.then(result => console.log(result));
flask_cors:
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
Always use dev_appserver.py for running your local development environment on GAE apps. GAE has a lot of peculiarities that are hard to reproduce manually on a local virtualenv. Plus you get a lot of useful tools to monitor various services (Taskqueues, Memcache, Storage, etc). dev_appserver.py also automatically loads a lot of GAE native apis for you to be able to use and very often they have their own versions of popular libs that are adapted for a serverless environment (URLFetch is one of them)
Official Docs
https://cloud.google.com/appengine/docs/standard/python/tools/using-local-server
I created 2 applications in my Azure directory, 1 for my API Server and one for my API client. I am using the Python ADAL Library and can successfully obtain a token using the following code:
tenant_id = "abc123-abc123-abc123"
context = adal.AuthenticationContext('https://login.microsoftonline.com/' + tenant_id)
token = context.acquire_token_with_username_password(
'https://myapiserver.azurewebsites.net/',
'myuser',
'mypassword',
'my_apiclient_client_id'
)
I then try to send a request to my API app using the following method but keep getting 'unauthorized':
at = token['accessToken']
id_token = "Bearer {0}".format(at)
response = requests.get('https://myapiserver.azurewebsites.net/', headers={"Authorization": id_token})
I am able to successfully login using myuser/mypass from the loginurl. I have also given the client app access to the server app in Azure AD.
Although the question was posted a long time ago, I'll try to provide an answer. I stumbled across the question because we had the exact same problem here. We could successfully obtain a token with the adal library but then we were not able to access the resource I obtained the token for.
To make things worse, we sat up a simple console app in .Net, used the exact same parameters, and it was working. We could also copy the token obtained through the .Net app and use it in our Python request and it worked (this one is kind of obvious, but made us confident that the problem was not related to how I assemble the request).
The source of the problem was in the end in the oauth2_client of the adal python package. When I compared the actual HTTP requests sent by the .Net and the python app, a subtle difference was that the python app sent a POST request explicitly asking for api-version=1.0.
POST https://login.microsoftonline.com/common//oauth2/token?api-version=1.0
Once I changed the following line in oauth2_client.py in the adal library, I could access my resource.
Changed
return urlparse('{}?{}'.format(self._token_endpoint, urlencode(parameters)))
in the method _create_token_url, to
return urlparse(self._token_endpoint)
We are working on a pull request to patch the library in github.
For the current release of Azure Python SDK, it support authentication with a service principal. It does not support authentication using an ADAL library yet. Maybe it will in future releases.
See https://azure-sdk-for-python.readthedocs.io/en/latest/resourcemanagement.html#authentication for details.
See also Azure Active Directory Authentication Libraries for the platforms ADAL is available on.
#Derek,
Could you set your Issue URL on Azure Portal? If I set the wrong Issue URL, I could get the same error with you. It seems that your code is right.
Base on my experience, you need add your application into Azure AD and get a client ID.(I am sure you have done this.) And then you can get the tenant ID and input into Issue URL textbox on Azure portal.
NOTE:
On old portal(manage.windowsazure.com),in the bottom command bar, click View Endpoints, and then copy the Federation Metadata Document URL and download that document or navigate to it in a browser.
Within the root EntityDescriptor element, there should be an entityID attribute of the form https://sts.windows.net/ followed by a GUID specific to your tenant (called a "tenant ID"). Copy this value - it will serve as your Issuer URL. You will configure your application to use this later.
My demo is as following:
import adal
import requests
TenantURL='https://login.microsoftonline.com/*******'
context = adal.AuthenticationContext(TenantURL)
RESOURCE = 'http://wi****.azurewebsites.net'
ClientID='****'
ClientSect='7****'
token_response = context.acquire_token_with_client_credentials(
RESOURCE,
ClientID,
ClientSect
)
access_token = token_response.get('accessToken')
print(access_token)
id_token = "Bearer {0}".format(access_token)
response = requests.get(RESOURCE, headers={"Authorization": id_token})
print(response)
Please try to modified it. Any updates, please let me know.