We've successfully implemented Azure AD B2C for authentication in our web application, and now I'm on to trying to use a built-in policy for Password Reset. The web app successfully captures the error code (AADB2C90118) thrown when the user clicks the Forgot Password link, redirecting to the Account/ResetPassword method which properly performs the Challenge, sending the user through the Password Reset experience.
The user gets validated and is able to change their password, but when the response is posted back to the web app, we end up in the AuthenticationFailed event with the following exception:
{"IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier\r\n (\r\n IsReadOnly = False,\r\n Count = 1,\r\n Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause\r\n )\r\n', \ntoken: '{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk\"}.{\"exp\":1537473897,\"nbf\":1537470297,\"ver\":\"1.0\",\"iss\":\"https://login.microsoftonline.com/1f0535de-b375-48bf-8bc8-d9e0b6ff185a/v2.0/\",\"sub\":\"454bd9a9-7a7c-4e7f-9535-213e07408d14\",\"aud\":\"f3afd0e2-9f6e-435a-918c-e8c542f9f5ad\",\"nonce\":\"636730670637853311.OTk5YTlhMWYtOTQyYi00ZDBjLTg0ZjItZDExMjI5MTQ5NzU1ZTY5MzZjODgtMmE2Yi00NmQ0LTg0MTYtOTc5YTJjNWEzOGQ4\",\"iat\":1537470297,\"auth_time\":1537470297,\"emails\":[\"ls1grrrl#gmail.com\"],\"oid\":\"454bd9a9-7a7c-4e7f-9535-213e07408d14\",\"name\":\"hbecker\",\"tfp\":\"B2C_1_SSPR\"}'."}
I can't seem to find anyone with this issue in the Password Reset process in B2C, so any help is appreciated!
In Startup.cs (ConfigureServices method)
services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = string.Format("https://login.microsoftonline.com/tfp/{0}/{1}/v2.0/", Portable.Constants.Constants.Tenant, Portable.Constants.Constants.PolicySignUpSignIn); options.Audience = Portable.Constants.Constants.ClientID; options.RequireHttpsMetadata = false; options.Events = new JwtBearerEvents { OnAuthenticationFailed = AuthenticationFailed }; });
In Configure method (before app.UseMvc();):
app.UseAuthentication();
Ensure that you also pass the correct metadata endpoint.
See this thread for the full workaround: https://github.com/Azure-Samples/active-directory-b2c-dotnetcore-webapi/issues/9
See also this very detailed thread discussing how to design the password reset policy to avoid this issue.
Related
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp()
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "a" })
.AddDistributedTokenCaches();
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
If i remove OpenIdConnectOptions events, i dont see any error. Is this a bug ?
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = (context) =>
{
if (context.Request.Headers.ContainsKey("X-Forwarded-Host"))
{
context.ProtocolMessage.RedirectUri = "https://" + context.Request.Headers["X-Forwarded-Host"] + Configuration.GetSection("AzureAd").GetValue<String>("CallbackPath");
}
return Task.FromResult(0);
}
};
});
MsalUiRequiredException
Error Code: AADSTS65001: The user or administrator has not consented
to use the application with ID '{appId}' named '{appName}'. Send an
interactive authorization request for this user and resource.
Mitigation
Get user consent first. If you aren't using .NET Core (which doesn't have any Web UI), call (once only) AcquireTokeninteractive. If you are using .NET core or don't want to do an AcquireTokenInteractive, the user can navigate to a URL to give consent: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id={clientId}&response_type=code&scope=user.read. to call AcquireTokenInteractive: app.AcquireTokenInteractive(scopes).WithAccount(account).WithClaims(ex.Claims).ExecuteAsync();
And also Try with enabling Multi-Factor Authentication on your tenant.
For more details refer this document
I am using AzureAD as a login provider for a .net core 3.1 app, using Microsoft.AspNetCore.Authentication.AzureAD.UI nuget package, and it is all set up and working.
AzureAD only allows users from the tenant access, if the user is not in the tenant then it shows them a sensible error page.
If a user has access to the site but needs a specific role to view a page they get redirected to /Account/AccessDenied/ page.
However if the user has no role assigned but is in the tenant, azure ad does a POST to my site's /signin-oidc/ with the following POST body:
error: interaction_required
error_description: AADSTS50105: The signed in user '{EmailHidden}' is not assigned to a role for the application
error_uri: https://login.microsoftonline.com/error?code=50105
My site then responds with a 405 method not allowed error.
I would like to handle this POST and provide a friendly error page saying the user needs to request access.
Settings defined in startup.cs already:
ConfigureServices:
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options =>
{
configuration.Bind("AzureAd", options);
});
services.Configure<CookieAuthenticationOptions>(AzureADDefaults.CookieScheme,
options => options.AccessDeniedPath = "/Account/AccessDenied");
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
// add name to User.Identity
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
};
options.TokenValidationParameters.ValidateIssuer = true;
});
services.AddMvc(options =>
{
// require user is logged in and has predefined role in order to access anything
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Role1", "Role2")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
Configure:
app.UseAuthentication();
app.UseAuthorization();
Have seen the following question but it seems as if I have already done what it suggests and it's not working.
My thoughts are that it never even reaches the AuthorizeFilter I've set up as AzureAD.UI just fails to handle the POST to singin-oidc
Just add an Event for your OpenIdConfiguration that will redirect to the proper page on some specific error from Azure.
options.Events.OnMessageReceived = async context =>
{
if (!string.IsNullOrEmpty(context.ProtocolMessage.Error) &&
!string.IsNullOrEmpty(context.ProtocolMessage.ErrorDescription) &&
context.ProtocolMessage.ErrorDescription.StartsWith("AADSTS50105"))
{
context.Response.Redirect("/Account/AccessRequest/");
context.HandleResponse();
return Task.CompletedTask;
}
}
I have two applications registered in Azure AD. One for front-end app and one for API(.NET Core). I need these applications to be multitenant. So I have set Accounts in any organizational directory (Any Azure AD directory - Multitenant) selection in AzureAD for both applications. I am using Azure AD V2.0 endpoints. I am able to get the id_token successfully from front-end app. But when it is passed to API, even though I have set TokenValidationParameters.ValidateIssuer to false, it tries to validate the issuer and returns 401 Unauthorized status. It seems TokenValidationParameters.ValidateIssuer flag is ignored.
I noticed a mismatch when I check the log
INFO Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.? [?] - MESSAGE: AzureADJwtBearer was not authenticated. Failure message: IDX10205: Issuer validation failed. Issuer: 'https://sts.windows.net/9967b0b6-c5d3-*************/'. Did not match: validationParameters.ValidIssuer: 'null' or validationParameters.ValidIssuers: 'https://sts.windows.net/{tenantid}/'.
2020-01-13 14:34:51,884 INFO Microsoft.AspNetCore.Authorization.DefaultAuthorizationService.? [?] - MESSAGE: Authorization failed
Please notice here the Issuer is https://sts.windows.net/9967b0b6-c5d3-*************/
But when I decode the id_token I can see the Issuer is set as "iss":"https://login.microsoftonline.com/9967b0b6-c5d3-*************/v2.0" which I think the correct end point for AzureAD V2.0. Could this be a reason for the above unauthorized return.
Below is the authentication related code in ConfigureServices method in Startup.cs
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
Below is the appSettings.json configuration
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "my_doamin.com",
"TenantId": "common",
"ClientId": "https://my_doamin.com/api-test"
}
The API end point with Authorize attribute
[Authorize(AuthenticationSchemes = "AzureADBearer")]
[Route("getPotalAdminUsers")]
[HttpGet]
public async Task<IActionResult> getPotalAdminUsers()
{
//Code
}
I went through some SO questions regarding this issue. But none helped me.
I appreciate any help on this. Thanks.
I found the solution and the reason behind the issue I faced.
I am posting the answer so that anyone who is facing the same issue can get the issue cleared out.
I was using OpenIdConnect middleware in Startup.cs which was the reason behind the issue. OpenIdConnect middleware is used when we need to sign-ing in the users from our app. But in our context we sign-ing in the users from the front-end app. API does not sign-ing in users! API just needs to validate the access_token.
To validate access_tokens we should use JwtBearer middleware instead of OpenIdConnect middleware.
So replace the
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
With
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
For more information refer the below link
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/How-ASP.NET-Core-uses-Microsoft.IdentityModel-extensions-for-.NET
I've followed the tutorial at https://blogs.msdn.microsoft.com/webdev/2017/10/17/user-accounts-made-easy-with-azure/#comment-312956 and am reasonably confident that I have set up my Azure B2C Tenant correctly - I have tested the login functionality through the Azure Portal, and all is good.
However, using the demo app, I am unable to sign in. I don't get any errors, and I can see through the browser dev tools that the request goes off to the portal, but it returns almost immediately without giving me the login page, and my app is still in the "sign in" state. Here's a screenshot of the network activity (FirefoxDeveloper)
There are no login activities recorded in the B2C portal and there are no errors anywhere...Any help would be gratefully received!
found the problem. The walkthrough for creating the B2C application does not mention that you need to append "/signin-oidc" to the Reply URL
The callback path is set in the OpenIdConnectOptions instance and defaults to "signin-oidc", e.g. taking the sample code
public void Configure(string name, OpenIdConnectOptions options)
{
options.ClientId = AzureAdB2COptions.ClientId;
options.Authority = AzureAdB2COptions.Authority;
// This is the default value
options.CallbackPath = "signin-oidc";
options.UseTokenLifetime = true;
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = OnRedirectToIdentityProvider,
OnRemoteFailure = OnRemoteFailure,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived
};
}
Whatever you set this to, you must align the ReplyUri in your Azure B2C tenant e.g.
http://myapp.com/signin-oidc
I am trying to use Azure Active Directory to perform login functions on my uwp app. This happens successfully however I cannot get it to refresh the token when it expires and always receive the error "Refresh failed with a 403 Forbidden error. The refresh token was revoked or expired." and so I have to bring up the login window again. I am using the version 2.1.0 and the following code to authenticate:
private async Task<bool> AuthenticateAsync(bool forceRelogon = false)
{
//string message;
bool success = false;
// Use the PasswordVault to securely store and access credentials.
PasswordVault vault = new PasswordVault();
PasswordCredential credential = null;
//Set the Auth provider
MobileServiceAuthenticationProvider provider = MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory;
MobileServiceUser user = null;
try
{
// Try to get an existing credential from the vault.
var credentials = vault.FindAllByResource(provider.ToString());
credential = credentials.FirstOrDefault();
}
catch (Exception ex)
{
// When there is no matching resource an error occurs, which we ignore.
Debug.WriteLine(ex);
}
if (credential != null && !forceRelogon)
{
// Create a user from the stored credentials.
user = new MobileServiceUser(credential.UserName);
credential.RetrievePassword();
user.MobileServiceAuthenticationToken = credential.Password;
// Set the user from the stored credentials.
App.MobileService.CurrentUser = user;
//message = string.Format($"Cached credentials for user - {user.UserId}");
// Consider adding a check to determine if the token is
// expired, as shown in this post: http://aka.ms/jww5vp.
if (RedemptionApp.ExtensionMethods.TokenExtension.IsTokenExpired(App.MobileService))
{
try
{
await App.MobileService.RefreshUserAsync();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
success = true;
}
else
{
try
{
// Login with the identity provider.
user = await App.MobileService
.LoginAsync(provider);
// Create and store the user credentials.
if (credential != null)
vault.Remove(credential);
credential = new PasswordCredential(provider.ToString(),
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
success = true;
//message = string.Format($"You are now logged in - {user.UserId}");
}
catch (MobileServiceInvalidOperationException)
{
//message = "You must log in. Login Required";
}
}
//var dialog = new MessageDialog(message);
//dialog.Commands.Add(new UICommand("OK"));
//await dialog.ShowAsync();
return success;
}
Can anyone see something wrong with what I am doing, or need to do anything within the AAD service provider?
You might be able to get more accurate information by taking a look at the server-side application logs. Token refresh failure details will be logged there automatically. More details on application logs can be found here: https://azure.microsoft.com/en-us/documentation/articles/web-sites-enable-diagnostic-log/. I recommend setting the trace level to Informational or Verbose.
Also, if you haven't done this already, Azure AD requires a bit of extra configuration to enable refresh tokens. Specifically, you need to configure a "client secret" and enable the OpenID Connect hybrid flow. More details can be found in this blog post: https://cgillum.tech/2016/03/07/app-service-token-store/ (scroll down to the Refreshing Tokens section and see where it describes the process for AAD).
Besides what has been said about mobile app configuration, I can spot this.
You have:
// Login with the identity provider.
user = await App.MobileService.LoginAsync(provider);
It should be:
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
new Dictionary<string, string>() {{ "response_type", "code id_token" }});
Maybe this will help:
https://azure.microsoft.com/en-us/blog/mobile-apps-easy-authentication-refresh-token-support/