I'm evaluating Entra External Id as an OAuth SSO provider for my web app that also allows the user to self register an account. I'm experiencing the following error when trying to receive an access token for users who have just self-registered with my tenant. Getting the access token for existing users works fine.
Does anybody know what might be the cause of this error and how to fix it?
This is an example error response with the trace and correlation ids censored.
{'error': 'invalid_grant', 'error_description': 'AADSTS131010: User not allowed by policy conditions. Trace ID: 00000000-0000-0000-0000-0000000000000 Correlation ID: 00000000-0000-0000-0000-0000000000000 Timestamp: 2024-02-05 23:20:24Z', 'error_codes': [131010], 'timestamp': '2024-02-05 23:20:24Z', 'trace_id': '00000000-0000-0000-0000-0000000000000', 'correlation_id': '00000000-0000-0000-0000-0000000000000', 'suberror': 'bad_token'}
I've tried a few things:
- I've gone through the login flow again in the same browser session after 15 minutes. I get the same error, so I don't think this is a timing issue.
- I've written my own code to redeem the OAuth token (rather than using MSAL) to confirm the parameters I was passing. In particular I tried with only the 'openid' scope, thinking an overly broad scope might have been the problem. Still got the error.
- I've signed in with the user I just registered in a separate browser session. This worked as expected and gave me the access token. Even after this successful sign in my original browser session returned the same error when I tried to sign in again. So the cookies in that browser session are 'bad' somehow.
- Looked at my Entra Conditional Access Policy settings. I don't believe I have anything set, in part because this feature is not supported by the free trial.
I've found a couple of similar questions on other forums which I think are the same problem. Neither have answers currently:
I've included the code I'm using to test this below. It's a Python Flask app which you can use to reproduce the issue as follows:
- Create an App Registration in your Entra External Id tenant. On the Authentication tab set the Redirect URI to 'http://localhost:8000/oauth/callback' and the supported account type to 'Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)'. Create a client id/client secret pair on the 'Certificates & Secrets' page.
- Install the requirements listed in the first block to your Python virtual environment.
- Run
AZURE_CLIENT_ID=... AZURE_CLIENT_SECRET=... AZURE_AUTHORITY=... python app.py
. Populating the ... with the appropriate values.
- Visit 'http://localhost:8000/login'.
- Create a new user when prompted and go through the flow to completion.
- You should be redirected to the app and will see an error like the above.
requirements.txt
msal==1.26.0
flask==3.0.1
app.py
import json
import os
import msal
from flask import Flask
from flask import (
Blueprint,
redirect,
make_response,
request,
)
views = Blueprint("views", __name__)
def build_msal_app():
# A value like 00000000-0000-0000-0000-000000000000
client_id = os.environ["AZURE_CLIENT_ID"]
# A value like g_123~exhw84r...
client_secret = os.environ["AZURE_CLIENT_SECRET"]
# A value like https://mytenant.ciamlogin.com/
authority = os.environ["AZURE_AUTHORITY"]
return msal.ConfidentialClientApplication(
client_id,
authority=authority,
client_credential=client_secret,
)
@views.route("/login")
def login():
app = build_msal_app()
redirect_url = app.get_authorization_request_url(
[],
redirect_uri="http://localhost:8000/oauth/callback"
)
return redirect(redirect_url)
@views.route("/oauth/callback")
def authorized():
code = request.args.get("code", "")
result = build_msal_app().acquire_token_by_authorization_code(
code,
scopes=[],
redirect_uri="http://localhost:8000/oauth/callback"
)
if "error" in result:
return json.dumps(result)
else:
return "access token: " + result["access_token"]
app = Flask(__name__)
app.register_blueprint(views)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8000)