Response URI for Azure AD B2C returns 404, custom OpenID identity provider - azure

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:

Related

Azure AD B2C with OpenID Connect getting error AADB2C90238: The provided token does not contain a valid issuer

I added and configured an OpenID Connect Identity Provider.
I set the return URL in the provider correctly.
I'm using the "Sign up and Sign in" user flow -- not a custom policy.
Running through the user flow, I ultimately get redirected to my application .../MicrosoftIdentity/Account/Error (or if I set return url to jwt.ms, I get the same error) with the page indicating the error
AADB2C90238: The provided token does not contain a valid issuer
How can I even see the issuer in the token? (It's all handled inside AD B2C service).
I can see what's listed in the provider's .../.well-known/openid-configuration endpoint. I guess that's what's not matching in the token. I've seen suggestions of using Application Insights Logs to view the token -- but, apparently, that can only be done with custom policies.
Is there another way to tell AD B2C not to validate the issuer? Or is another way to handle this issue?
I tried to reproduce the same in my environment.
Open Id configuration is like below:
Where the metadata url is https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration
Authorization request looks like below:
https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_newSignupSignin&client_id=xxxxx5&nonce=defaultNonce&redirect_uri=https%3A%2F%2Fjwt.ms&scope=openid&response_type=id_token&prompt=login
I received the same error :
With redirect uri: https://jwt.ms
Error: invalid_request
AADB2C90238: The provided token does not contain a valid issuer. Please provide another token and try again.
With redirect uri: https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/authresp
So here the redirect Uris are correct and need to correct the metadata url :
Created an OpenId provider with meta data url having tenantId instead of organizations .
https://login.microsoftonline.com/<tenantId>/v2.0/.well-known/openid-configuration
Run the user flow with this Identity provider
Could login successfully and get the access token with endpoint
Note: make sure it has the policy included:
I have p=B2C_1_newSignupSignin
https://kavyasarabojub2c.b2clogin.com/kavyasarabojub2c.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_newSignupSignin&client_id=1xxxxe2a5&nonce=defaultNonce&redirect_uri=https%3A%2F%2Fjwt.ms&scope=openid&response_type=id_token&prompt=login
Here the issuer is of V2 endpoint "iss": "https://kavyasarabojub2c.b2clogin.com/<tenantId>/v2.0/"
Reference : Web sign in with OpenID Connect - Azure Active Directory B2C | Microsoft Learn
Edit:

Unable to set client claims when acquiring confidential client application token

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

Wrong access_token from AAD with OAuth2 flow

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).

Azure AD Multi-tenant Applications - How to implement Token Validation?

I have a multi tenant Web app / API registered in Azure ad and that connected to an API App. The API App has Active Directory Authentication setup. At the moment only one other tenant needs access to api. I made sure only they can get access by putting https://sts.windows.net/<third party tenant>/ in the Issuer URL. My question is: How would I go about giving a second (or more) tenants access to the api? I can't add anymore tenant ids in the Issuer URL so I'm kinda at a loss
Thanks
The approach you are using currently will work only in a single tenant scenario, i.e. Automatic validation of tenant by setting IssuerURL works only in a single tenant scenario.
In case of multi-tenant applications, the application is responsible for storing and validating all possible issuers. This is by design and exact guidance on this topic from Microsoft is available here:
Work with claims-based identities in Azure AD: Issuer Validation
For a single-tenant application, you can just check that the issuer is
your own tenant. In fact, the OIDC middleware does this automatically
by default. In a multi-tenant app, you need to allow for multiple
issuers, corresponding to the different tenants. Here is a general
approach to use:
In the OIDC middleware options, set ValidateIssuer to false. This turns off the automatic check.
When a tenant signs up, store the tenant and the issuer in your user DB.
Whenever a user signs in, look up the issuer in the database.If the issuer isn't found, it means that tenant hasn't signed up. You
can redirect them to a sign up page.
You could also blacklist certain tenants; for example, for customers that didn't pay their subscription.
So, in case of a .NET based web application the code in your startup class would change to something like this.. notice the new TokenValidationParameters { ValidateIssuer = false }
Authenticate using Azure AD and OpenID Connect
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions {
ClientId = configOptions.AzureAd.ClientId,
ClientSecret = configOptions.AzureAd.ClientSecret, // for code flow
Authority = Constants.AuthEndpointPrefix,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
PostLogoutRedirectUri = configOptions.AzureAd.PostLogoutRedirectUri,
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false },
Events = new SurveyAuthenticationEvents(configOptions.AzureAd, loggerFactory),
});
Once you have disabled the Validate issuer, you will need to handle the validation yourself. Here is a sample with some guidance around how to do this validation yourself
Update your code to handle multiple issuer values
You will at least need to check the "tid" claim which captures the Azure AD Tenant Id against your own list of valid tenant IDs, before you let the call go through.
When a single tenant application validates a token, it checks the
signature of the token against the signing keys from the metadata
document, and makes sure the issuer value in the token matches the one
that was found in the metadata document.
Since the /common endpoint doesn’t correspond to a tenant and isn’t an
issuer, when you examine the issuer value in the metadata for /common
it has a templated URL instead of an actual value:
https://sts.windows.net/{tenantid}/
Therefore, a multi-tenant application can’t validate tokens just by
matching the issuer value in the metadata with the issuer value in the
token. A multi-tenant application needs logic to decide which issuer
values are valid and which are not, based on the tenant ID portion of
the issuer value.
For example, if a multi-tenant application only allows sign in from
specific tenants who have signed up for their service, then it must
check either the issuer value or the tid claim value in the token to
make sure that tenant is in their list of subscribers. If a
multi-tenant application only deals with individuals and doesn’t make
any access decisions based on tenants, then it can ignore the issuer
value altogether.
(EDIT) More information on Validating Tokens
I'm trying to answer your questions from comments here.
Here is sample code which does the task of manually validating JWT tokens.
Manually validating a JWT access token in a web API
A useful excerpt..
Validating the claims When an application receives an access token
upon user sign-in, it should also perform a few checks against the
claims in the access token. These verifications include but are not
limited to:
audience claim, to verify that the ID token was intended to be given
to your application not before and "expiration time" claims, to verify
that the ID token has not expired issuer claim, to verify that the
token was issued to your app by the v2.0 endpoint nonce, as a token
replay attack mitigation You are advised to use standard library
methods like JwtSecurityTokenHandler.ValidateToken Method
(JwtSecurityToken) to do most of the aforementioned heavy lifting. You
can further extend the validation process by making decisions based on
claims received in the token. For example, multi-tenant applications
can extend the standard validation by inspecting value of the tid
claim (Tenant ID) against a set of pre-selected tenants to ensure they
only honor token from tenants of their choice.
Sample Access Token, just for understanding: Access Token and Id_token are both simple base64 encoded JSON Web Tokens (JWT). You can decode these to find the claims and then validate them. I'm sharing a sample which has code to do just that. Before that here is a sample access token from one of Microsoft Docs. I just took one for example from here
Actual Value: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1N... (a long encoded string continues)
Decoded Value (you can check this easily using a website like https://jwt.io):
{
"aud": "https://service.contoso.com/",
"iss": "https://sts.windows.net/7fe81447-da57-4385-becb-6de57f21477e/",
"iat": 1388440863,
"nbf": 1388440863,
"exp": 1388444763,
"ver": "1.0",
"tid": "7fe81447-da57-4385-becb-6de57f21477e",
"oid": "68389ae2-62fa-4b18-91fe-53dd109d74f5",
"upn": "frankm#contoso.com",
"unique_name": "frankm#contoso.com",
"sub": "deNqIj9IOE9PWJWbHsftXt2EabPVl0Cj8QAmefRLV98",
"family_name": "Miller",
"given_name": "Frank",
"appid": "2d4d11a2-f814-46a7-890a-274a72a7309e",
"appidacr": "0",
"scp": "user_impersonation",
"acr": "1"
}
As you can see the decoded value has many claims including "tid" which you're about to validate.
Hope this helps!

401 unauthorize exception for multitenant web api

Need help in authenticating the token request from any client application to WEB API. We have registered our web API has multi-tenant application in Azure AAD. My client application which has been registered in different tenant was able to get Access token from AAD.while making Http request to our endpoint with passing the access token part of request header, we are receiving 401 unauthorized exception. I found reason while browsing for multi tenant scenario is to disable ValidateIssuer and have custom handler.
• Is there any custom handler on implementing for WindowsAzureActiveDirectoryBearerAuthentication. I see people are using OpenIDConnect. But for WEB API, we are using WindowsAzureActiveDirectoryBearerAuthentication i.e Is there any equivalent Event for validation of access token in UseWindowsAzureActiveDirectoryBearerAuthentication and tell user is authenticated ?.
• Is there any better standard of validation of access token and tell user is valid user ?.
• Can we get the claims of user by passing bearer token to WEBAPI Authorize filter ?. or will httprequest object claims gets user information like given name, tenant name, object ID (esp. localhost debugging scenario also.), If we can get those information, we can have our own logic of validation.
Please let us know whether this approach is best practice for authenticating a user.
You could implement a custom issuer validator and assign it to the IssuerValidator property. This is useful when you can't specify a predefined list of issuers in configuration and need some runtime logic to determine if you trust the issuer presented in the token:
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
IssuerValidator = (issuer, token, tvp) =>
{
if (db.Issuers.FirstOrDefault(b => (b.Issuer == issuer)) == null)
return issuer;
else
throw new SecurityTokenInvalidIssuerException("Invalid issuer");
}
}
You could decode the access token to get basic user information like family_name/given_name , but you can only get that by using user identity to acquire the access token .

Resources