I am attempting to use the MSAL python library to call another custom api in Azure(Exposed through AppRegistration with an API scope exposed).
I am writing a daemon application that will make the request.
Following Azure documentation here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-app-configuration?tabs=python
The last example on this Azure docs suggests you can add assertions about custom claims such as client_ip that would be returned in the token.
Similarly, I would like the preferred_username claim to be set to Test as an example:
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential={"thumbprint": config["thumbprint"], "private_key": open(
config['private_key_file']).read()},
client_claims={"preferred_username": "Test"}
)
However, When I acquire the token using the following code, the preferred_username claim is not within the Token.
result = app.acquire_token_for_client(scopes=config["scope"])
Within the app registration for the daemon app I have added preferred_username as an optional claim (for access tokens).
I am not sure what is wrong with my approach or if I have misinterpreted the intent of client_claims?
I tried to reproduce the same in my environment and got the results like below:
I created an Azure AD Application and configured custom preferred_username claim:
I generated the token via Postman by using below parameters:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default
grant_type:client_credentials
Optional claims are not included in the token like below:
Note that: Getting optional claim is only possible with Authorization code flow, ROPC flow, Implicit flow. Currently, Client Credentials flow does not support adding any additional custom claims.
Client Credentials flow fetch the token in the application's context and won't have any user-related claims like preferred_username, given_name or email, etc. So, you have to generate the token in the user's context to get the claims.
Alternatively, I generated the Access Token using the endpoint like below:
https://login.microsoftonline.com/tenantID/oauth2/v2.0/authorize?client_id=ClientID&response_type=token&redirect_uri=redirecturi&scope=user.read&response_mode=fragment&state=12345&nonce=678910
Optional claims are included in the token like below:
Reference:
Client assertions (MSAL) - Microsoft Entra | Microsoft Learn
Related
We're implementing a custom identity provider for Azure AD B2C, using OpenID protocol option, as a generic OpenID Connect.
Everything works as expected until it's time to post the response back to Azure AD B2C using the redirect URI provided. I've found documentation regarding expected structure of this response URL, and what we see in the documentation is identical to what Azure AD B2C specifies when it issues the authentication sequence.
Configured values:
Response type: code
Response mode: form_post
User ID claim: sub
Display name claim: name
When the custom identity provider GETs or POSTs authentication response (code) back to https://REDACTED.b2clogin.com/REDACTED.onmicrosoft.com/oauth2/authresp, the Azure B2C returns 404.
Note that this is not 400, not 401, not 403, not 5xx. It is precisely 404 (not found), with a basic text (non-html) content saying resource not found. This response looks to me very much like a misconfigured API management layer on Azure side, hitting a wrong internal URL.
We're expecting that the URL https://REDACTED.b2clogin.com/REDACTED.onmicrosoft.com/oauth2/authresp actually works. It looks like what the expected Azure AD B2C response endpoint is from documentation, and it is also exactly what Azure AD B2C itself specifies when initiating the OpenID sequence with our custom identity provider web application.
So far we were unable to find the root cause, nor even any useful input beyond raw network request logs (case with Microsoft support was open since 2023-01-23). The last resort could be re-creating the B2C tenant, since this feature seems to work for other people, but that would require migration and significant down time on our end.
SOLUTION: The response to AD B2C authresp endpoint was missing 'nonce' claim (in the id_token payload), and 'state' parameter in the HTTP request. Both values are supplied by AD B2C when initiating authorization. As soon as custom identity provider started properly adding those two values, error 404 went away.
Response should include supplied nonce as a claim inside the id_token payload, and supplied state as HTTP request parameter or query string
https://openid.net/specs/openid-connect-basic-1_0.html
I had the same issue (a 404 error as a result of the /authresp POST from my custom OIDC IdP back to Azure AD B2C using the redirect URI Azure AD B2C had just provided as a query parameter on the /authorize request to my IdP: redirect_uri=https://mytenant.b2login.com/mytenant.onmicrosoft.com/oauth2/authresp
In my case (using an implicit flow), it was about properly handling the "nonce" query parameter on the inbound /authorize request (from Azure AD B2C to my IdP) by ensuring the generated id_token it returned included the nonce as a claim.
In your case (using an authorization code flow...and assuming you also return an id_token based on the "sub" and "name" claims you're returning), your /token endpoint needs to include the nonce inside the id_token...so propagate the nonce (and state) as query parameters along to your /token endpoint via the /authorize to /token redirect method you use.
If a federated IdP doesn't include the nonce as a claim inside the id_token payload that it returns, Azure AD B2C will return a 404 error from the /authresp request.
I don't know why Microsoft chose to return a 404 instead of a more informative "nonce invalid" error message, or at least, a 400 error...perhaps it's for the same security reason a login form doesn't precisely tell you when your password is invalid.
In the OpenID Connect specification, the nonce description (under IDToken) states (bolding is my doing):
String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token. If present in the ID Token, Clients MUST verify that the nonce Claim Value is equal to the value of the nonce parameter sent in the Authentication Request. If present in the Authentication Request, Authorization Servers MUST include a nonce Claim in the ID Token with the Claim Value being the nonce value sent in the Authentication Request. Authorization Servers SHOULD perform no other processing on nonce values used. The nonce value is a case sensitive string.
Although the spec indicates a nonce is optional, Microsoft is following best practices by supplying one...and since Azure AD B2C (as the Authorization Server) gets the id_token from the IdP, it requires a federated OIDC IdP to play by the same rule.
In case this helps others, my custom IdP's /.well-known/openid-configuration endpoint returns:
{
"authorization_endpoint": "https://myidp.azurewebsites.net/oauth2/authorize",
"authorization_response_iss_parameter_supported": true,
"claims_parameter_supported": false,
"claims_supported": [
"aud",
"idp",
"iss",
"iat",
"exp",
"nonce",
"s-hash",
"sid",
"sub",
"auth_time",
"email",
"family_name",
"given_name",
"locale",
"name",
"updated_at",
"user_id"
],
"claim_types_supported": ["normal"],
"grant_types_supported": ["implicit"],
"id_token_signing_alg_values_supported": ["RS256"],
"issuer": "https://myidp.azurewebsites.net",
"jwks_uri": "https://myidp.azurewebsites.net/oauth2/jwks",
"response_modes_supported": ["form_post"],
"response_types_supported": ["id_token"],
"scopes_supported": ["openid"]
}
(Yes, my IdP runs on an Azure App server...but, "myidp" isn't my real tenant name.)
p.s. Currently, my IdP is used exclusively in a federation with AzureAD B2C (which acts as the Authorization Server for my client application via the MSAL library), so my IdP simply supports just an implicit flow and three endpoints (/.well-known/openid-configuration, /jwks and /authorize). If it were a general purpose IdP, or allowed direct client requests, it would support other flows (e.g. an authorization code flow), additional scopes (beyond "openid"...e.g. "profile") and additional endpoints (e.g. /token and /userinfo). However, regardless of flow, as long as an id_token is returned, it needs to include the nonce in its payload.
To troubleshoot the issue, I would recommend the following steps:
Verify that the redirect URI you are using is correct and matches
the one specified by Azure AD B2C.
Check that the response type and response mode specified in your
custom identity provider match the values expected by Azure AD B2C.
Verify that the claims you are sending in the response (e.g. "sub"
and "name") match the expected format and values for Azure AD B2C.
Check the network request logs for any additional information that
might help identify the issue.
If possible, try to isolate the issue by testing the authentication
flow with a minimal configuration to determine if the problem is
with your custom identity provider or with Azure AD B2C.
If the issue persists after trying these steps, you may want to consider reaching out to Microsoft support for further assistance.
I tried to reproduce the scenario in my environment:
Make sure the endpoint to which I requested the authorization url
It includes policy and with
redirect URI= https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/authresp
User Flow is of SignupSignin and not just Signin
Make sure to include all the required api permissions , importantly make sure to include openid , profile
I Configure idp such that , userId is mapped to oid.
The authorization url must have the policy included .
Here I have B2C_1_SignupSignin policy set for the User flow.
redirect URI= https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/authresp
Auth url:
https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_newSignupSignin&client_id=xxx&nonce=defaultNonce&redirect_uri=https%3A%2F%2Fxxxb2c.b2clogin.com%2Fxxxb2c.onmicrosoft.com%2Foauth2%2Fauthresp&scope=openid&response_type=id_token&prompt=login
When profile scope is not given I got bad request
But when openid and profile along with Directory.Read.All api permissions are included, the request run successfully.
Note: metadata url must be : https://login.microsoftonline.com/<tenantId>/v2.0/.well-known/openid-configuration
Successfully logged in and got the token containing idp_access_token
Identity provider access token , decoded and got the user claims:
I am working on a solution that needs to retreive email from a mailbox from a tenant using MS Graph API.
The solution needs to run in the background, with no use input (eg call login page for email in browser)
I have registered an Azure application, and have Admin consent for the MS Graph API:
Api permissions
My VB code is as follows (client_id, client_secret, tenant has been removed as confidential):
Dim http As New Chilkat.Http
Dim req As New Chilkat.HttpRequest
Dim json As New Chilkat.JsonObject
' Use the application ID for the client_id.
' (In Azure App Registrations, use the Application (client) ID)
req.AddParam(client_id, )
req.AddParam(client_secret, )
req.AddParam(tenant, )
req.AddParam(scope, https://graph.microsoft.com/.default)
'req.AddParam(username, )
'req.AddParam(password, )
req.AddParam(grant_type, client_credentials)
Dim resp As Chilkat.HttpResponse
' Replace {tenant} with your tenant ID, such as 112d7ed6-71bf-4eba-a866-738364321bfc.
resp = http.PostUrlEncoded(https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token, req)
If (http.LastMethodSuccess True) Then
Debug.WriteLine(http.LastErrorText)
Exit Sub
End If
Dim statusCode As Integer = resp.StatusCode
Debug.WriteLine(response status code: statusCode)
Debug.WriteLine(response body:)
Debug.WriteLine(resp.BodyStr)
I can see the above connecting to the Azure app. However, no permission (scope) is returned in the access token.
When I connect to MS Graph via browser, the token has all the required scope.
Can you please help/advise why I am unable to retreive token with permissions from the MS Graph API in vb?
Please note that, you cannot get scp claim in the token, as
user is not involved in generating token from client_credentials
grant type.
I assigned the same API permissions to my application like below:
When I decoded the access token(generated using client credentials grant type), scp claim is not present in the token like below:
You have to use authorization code flow grant type to get
scp claim in the decoded token, where user interaction is involved.
When I decoded the access token generated using authorization code flow, got scp claim successfully in the token like below:
To know more about authorization code flow, please refer below link:
Microsoft identity platform and OAuth 2.0 authorization code flow - Microsoft Entra | Microsoft Docs
You are using a client_credentials flow, which is an app-only context authentication.
The scp claim is only for user based authentication.
If you want to use the client_credentials flow, you will need to grant Application permissions on your app registration, instead of Delegated.
These applications permissions will then be present inside a roles claim. You can check the description of these claims here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens
When using the MSAL library to generate access token for a background console application, using client_credentials, to call two REST endpoints, the get token call is created as:
client_id=XXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-ClientServices%2F.default+api%3A%2F%2FMyService-DevBlr-CoreServices%2F.default&grant_type=client_credentials
and posted to
https://login.microsoftonline.com/a1326aec-378e-4433-8e2a-67a086554fc8/oauth2/v2.0/token
The response is a 400 error:
{"error":"invalid_scope","error_description":"AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://MyService-DevBlr-ClientServices/.default api://MyService-DevBlr-CoreServices/.default is not valid.\r\nTrace ID: 203ad632-5e7c-4e61-9dcd-185eb9b49200\r\nCorrelation ID: 99fd979a-2c11-485b-ba14-c0cad3312e02\r\nTimestamp: 2020-01-11 08:36:29Z","error_codes":[70011],"timestamp":"2020-01-11 08:36:29Z","trace_id":"203ad632-5e7c-4e61-9dcd-185eb9b49200","correlation_id":"99fd979a-2c11-485b-ba14-c0cad3312e02"}
When individual scopes are passed in, the call works ok:
Request:
client_id=XXXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-CoreServices%2F.default&grant_type=client_credentials
Response:
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0e..."}
and
Request:
client_id=XXXX&client_info=1&client_secret=XXXX&scope=api%3A%2F%2FMyService-DevBlr-ClientServices%2F.default&grant_type=client_credentials
Response:
{"token_type":"Bearer","expires_in":3599,"ext_expires_in":3599,"access_token":"eyJ0eX..."}
The documentation and the API supports scopes array:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-credential-flows#code-snippet
There is benefit in getting a single token to be used with multiple service calls, but I can't seem to get this working. Any suggestion is appreciated.
When you acquire an access token, you can only specify scopes for one API.
It seems to me you are trying to acquire a token with permissions to 2 APIs.
This is not possible.
An access token always only contains permissions to one API.
So you must request them separately.
This is due to a token having an audience (aud) claim that identifies the API it is meant for.
It cannot have two values.
I am making OAuth 2.0 auth code authentication flow with multi-tenant application.
Here is my authorize url:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=my_id&prompt=consent&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauthorize&response_type=code&scope=openid+offline_access&state=17
It goes fine and I receive auth_code. Then I make request with this auth_code to token_url and receive a lot of information, like:
token_type
scope
id_token
access_token
refresh_token
expires_at
ext_expires_in
Seems fine to me, but when I make request on API with access_token like:
https://management.azure.com/subscriptions/my_sub_id/locations?api-version=2016-06-01
with headers:
Content-Type:
- application/json
Authorization:
- Bearer EwBQA8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAV1IHgHb4dOWblzfd/YsSuFicAMDYbua17QivnAT9/pIaeKAg3uKsK5VGqWLzjMOUQrCpd7R1RAM6RkzI0u8e4rpO7DISG7qLso5H5+U1jb+38/j1urcwlXMMxhy83ZXmdpkLXpZV+vcOV...
It responds with 401 error
body:
encoding: UTF-8
string: '{"error":{"code":"InvalidAuthenticationToken","message":"The access token is invalid."}}'
To be honest I think something wrong with my access_token. It seems not like JWT for me. Documentation says it looks like:
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCEV1Q..."
But my access_token looks like:
"access_token": "EwBYA8l6BAAURSN/FHlDW5xN74t6GzbtsBBeBUYAAZDe7JE/MPLoAi+Fr+1Xxq5eBe5N9l8Q+c4QjkY5PGEzRnBpPe7+v6h+PLdh1cceBQx+/JsB2QCrYSCt7x/zGsQAhwoY/"
Is it fine?
Here is my permissions for application:
Permissions
The main issue you have here is that you have only asked for an access token for the scopes openid offline_access. The resulting access token will be for Microsoft Graph (https://graph.microsoft.com), not for the Azure REST API (https://management.azure.com).
To indicate you would like a token for a given API, the scope parameter in your authorization request should include the delegated permission you would like the app to have for the API. In the case of Azure REST API, there's only one delegated permission: user_impersonation. The identifier URI for the Azure REST API is https://management.azure.com, so the scope value you want to use is:
openid offline_access https://management.azure.com/user_impersonation
Two more important notes:
As you've discovered, you will not always be issued an access token as a JWT which you can decode peek at. The format of the access token is an agreement between the service which issued the token (Azure AD or Microsoft Accounts, in this case), and the service for which the token was issued (Microsoft Graph, in this example).
You should not always include prompt=consent. prompt=consent should only be used if you have already tried signing in the user without the user needs to be re-prompted for consent for a new permission.
If you simply include the required scopes in the scopes parameter, the Microsoft Identity platform will take care of figuring out if it needs to prompt for consent or not. If you always include prompt=consent, you will find that many organizations will be blocked from accessing your app, because they've disabled the ability for users to grant consent themselves (and this parameter specifically states that you require the user to be prompted again).
Our web aap is authenticating with the Azure AD via SAML2.0 similar to this.
In return we get SAML assertion(SAML token).
But when the user who logs in have more then 150+ groups the response doesn't contain the group information(so that token size doesn’t exceed HTTP header size limits. More info on this)
But what it return is a Graph Api to be hit to get the group information something like https://graph.windows.net/{tenant id}/users/{user id}/getMemberObject.
By going through this
I understand that I need to attach a Auth bearer token with the http request to hit the graph api successfully.
My problem is how do I get the Auth bearer token?
How can I use the SAML token to get the Auth bearer token?
Other useful link - link1 link2
I've only used the non SAML graph API using the ADAL libraries but from the docs it appears the NameID seems to be the basis for requesting an access token for the Graph API:
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">m_H3naDei2LNxUmEcWd0BZlNi_jVET1pMLR6iQSuYmo</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
</Subject>
From this post,
"Here the Client gets a SAML bearer assertion from the SAML Identity
Provider then requests an access token from the Authorisation Server
using the SAML bearer assertion as proof of identity"
and this article states the entire Assertion is used to get the access token, where you:
encode the whole assertion by using base64url encoding before adding
it as part of the POST request
It appears that exchanging a SAML token for a Graph access token is only supported for AD FS, not Azure AD. As per:
This scenario works only when AD FS is the federated identity provider that issued the original SAMLv1 token. You cannot exchange a SAMLv2 token issued by Azure AD for a Microsoft Graph access token.
Source: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-saml-bearer-assertion.
In general, you have to add the OIDC/OAuth stack to your app. As I understand it, this is in addition to your existing SAML authentication implementation. See: https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-token-exchange-saml-oauth