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'm trying to use ADB2C custom policies with the MSAL.js library on a static web app
Our policies are reasonably complex, so we've split the 'signup' into it's own flow, but now I'm having trouble with the handover from signup to signin. From what I'm reading it sounds like each IEF policy assigns its own tokens, and so a token generated from a signup flow cannot be used to signin? Even if they're both associated with the same ADB2C tenant?
Is this correct? It seems odd if so, as the keys (behind the discovery document/jwks_uri) are identical on both policies, as is the issuer.
The error that msal reports is during an 'acquireTokenFromNetworkStart' request which returns a 400 Bad Request Silent SSO could not be completed - insufficient information was provided. Please provide either a loginHint or sid.
So perhaps I just need to adjust the session management on the ADB2C policies? Do I need to emit the sid (session id) and then use that with msal when re-acquiring tokens?
Any advice would be most welcome, I cannot find a well documented example that puts all of this together.
Please check if it can be worked around like below .
Try to include your scope in the initial loginRedirect or loginPopup or while calling acquireTokenRedirect or acquireTokenPopup before calling acquireTokenSilent.
Scopes that is created in expose an api like "user.read", "mail.send" in your login request in code and grant consent for the same .
That means we need to Call acquireTokenSilent with your resource scope.
var loginRequest = {
scopes: ["openid","user.read", "mail.send"]
};
try {
msalInstance.loginRedirect(loginRequest);
} catch (err) {
// handle error
}
In B2C ,your tenant will need to be configured to return the emails claim on idTokens .Reference
Also try to include application Id or app id uri in the scopes .
References:
microsoft-authentication-library-for-js/issues
cannot-get-access-token-in-react-app/SO Reference
I am trying to generate a token for a user with below code.
string apiResourceId = "11224320-66b9-4132-8953-9aa485f07004";
string clientId = "bc9869a0-2393-4e42-8c52-845071640ea8";
Uri redirectUri = new Uri("https://localhost:44335/");
string authority = string.Format("https://login.windows.net/{0}",
"rudderless.onmicrosoft.com");
var authContext = new AuthenticationContext(authority);
AuthenticationResult authenticationResult;
authenticationResult = await authContext.AcquireTokenAsync(apiResourceId, clientId,
redirectUri, new PlatformParameters(PromptBehavior.Auto, null));
I have been getting an error in AcquireTokenAsync call -
AADSTS70002: The request body must contain the following parameter:
'client_secret or client_assertion'. Trace ID:
a198696d-8377-40eb-8351-527a25183500 Correlation ID:
24d4b47d-67bf-46c0-a6b7-a248c434512e Timestamp: 2017-09-20 23:09:38Z
Why do I need a client_secret or client_assertion if I want to generate a token when a user is authenticated against a AAD? The type of Client I am using is "Web app /API". However when I am trying to use a Native client I get the token generated but API call to apResourceID is generating unauthorized error.
Few Questions I am seeking help on related to the scinario -
Why I need to provide client_secret when I am using user auth flow?
Why AcquireToken succeed when I change the client Type to Native?
Why the token generated through native client gives an Unauthorize error?
Is there a way for admin to consent on behalf of every user in AAD?
Why I need to provide client_secret when I am using user auth flow?
Web Apps and APIs are considered Confidential Clients. See here for a definition of the different Client Types in the OAuth 2 Specification. These kinds of client always need to use their client secret to authenticate, no matter the flow they are following.
Confidential clients are typically issued (or establish) a set of
client credentials used for authenticating with the authorization
server (e.g., password, public/private key pair).
Why AcquireToken succeed when I change the client Type to Native?
Native Client Applications are a subset of Public Clients. These are defined, in the specification as:
Clients incapable of maintaining the confidentiality of their
credentials (e.g., clients executing on the device used by the
resource owner, such as an installed native application or a web
browser-based application), and incapable of secure client
authentication via any other means.
Therefore, they do not have or need a client_secret to authenticate... but this also means they can only authenticate with user context, whereas a confidential client could authenticate without a user present (Client Credential Flow).
Why the token generated through native client gives an Unauthorize
error?
This is hard to answer without knowing more about the error and the call you are making that causes this error. You should provide more information about this scenario.
Is there a way for admin to consent on behalf of every user
in AAD?
Yes. In the new Azure Active Directory V2 Endpoint, we have an "Admin Consent Endpoint".
Using the older V1 endpoint, we have an &prompt=admin_consent query string which you can read about here.
I have set up a new web app to be able to use the Oauth2 V2 authorization endpoint. I defined the app in https://apps.dev.microsoft.com/
If I want to obtain a new authorization token, following instructions in
https://blogs.msdn.microsoft.com/richard_dizeregas_blog/2015/09/04/working-with-the-converged-azure-ad-v2-app-model/
I get the following error on the login page:
Sorry, but we’re having trouble signing you in.
We received a bad request.
Additional technical information:
Correlation ID: eb9c2331-32bd-45a9-90d1-e9105f0bfa87
Timestamp: 2016-05-22 18:10:48Z
AADSTS70011: The provided value for the input parameter 'scope' is not valid. The scope https://graph.microsoft.com/Calendar.Read is not valid.
The scope is taken from an example in :
https://github.com/Azure/azure-content/blob/master/articles/active-directory/active-directory-v2-scopes.md
So I imagine it is a valid scope.
In v1 of the OAuth2 protocol, it was necessary to configure access to APIs in the Azure AD of my tenant, prior to using them. So I attempted to do so for the new application.
Attempting to do so, the Azure application management reports an error:
{
"message":"This request has a value that is not valid.",
"ErrorMessage":"This request has a value that is not valid.",
"httpStatusCode":"InternalServerError","operationTrackingId":null,"stackTrace":null,"Padding":null
}
What is missing to be able to use the new authorization endpoint ?
The documentation contains a typo if states calendar.read. It must be calendars.read:
private static string[] scopes = {
"https://graph.microsoft.com/calendars.readwrite"};
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(scopes, additionalScopes, clientId, redirectUri, UserIdentifier.AnyUser, null);
I'm playing with Azure AD authentication and authorization scenarios and not all clear for me.
Currently I'm looking at SinglePageApp-WebAPI-AngularJS-DotNet sample project.
And one thing that I can't understand. On the client side we get access token using implicit grant flow - that is clear. Then we send request to WEB API with Bearer and access token as value. On the server side we have TodoListController with Authorize attribute. So, in order to hit the controller request must be authorized.
What is not clear how authentication middleware validates my access token (what if random text sent instead of valid access token?). I cloned katana project and investigated WindowsAzureActiveDirectoryBearerAuthenticationExtensions and OAuthBearerAuthenticationHandler classes, but I still can't find concrete place where validation occur.
How my WEB API will know that this particular access token is valid and where is this concrete line of code, which is responsible for validation?
Thanks!
UPD:
Actually I find the place. All work is done inside OAuthBearerAuthenticationHandler in method AuthenticateCoreAsync:
tokenReceiveContext.DeserializeTicket(tokenReceiveContext.Token);
this call leads to running JwtFormat.Unprotect method, which performs actual validation.
Thanks #Aram for good notes.
In your service start up you register OWIN and when your controller is decorated with Authorize attribute then the authentication challenge will happen and OWIN will try to validate the token on each request..
The token validation happens because you have OWIN Dll references AND you have the startup.auth class in your service project...
You probably have something like this, that you include Auth challenge in the service side:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
});
When the validation is happening, the OWIN middleware will validate against the Audience and the Tenant that the token has been acquired from, and if they dont match the Authz will fail...
Depending on with Authentication handler you use the actual code that calls the ValidateToken function is in YOUR_AUTH_HANDLERAuthenticationHandler class.
Here is the location for OpenId AuthZ handler:
http://katanaproject.codeplex.com/sourcecontrol/latest#src/Microsoft.Owin.Security.OpenIdConnect/OpenidConnectAuthenticationHandler.cs
For instance if you have OpenIdConnect Authz handler then the token validation is in : Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationHandler class and the Overrride method :
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
And the ValidateToekn code will look like (if your AuthZ handler is OpenId connect):
ClaimsPrincipal principal = Options.SecurityTokenHandlers.ValidateToken(openIdConnectMessage.IdToken, tvp, out validatedToken);