I have a Web API built using ASP.NET Core. I have a React App that will call this API. The identity is managed using AAD B2C. I am running into an issue where the bearer token generated by the app is not recognized by the API.
I am certain that this has to do with my settings because the token itself has all the claims I need (as decoded by JWT.io). However, when I pass it through the code in .NET Core to allow authorization, the ClaimsIdentity has nothing and contains no user information.
I am setting up the instance using the following lines of code:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
configuration.Bind("AzureAdClient", options);
options.TokenValidationParameters.NameClaimType = "name";
}, options => { configuration.Bind("AzureAdClient", options); });
I also have the following configuration:
"AzureAdClient": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "somename.onmicrosoft.com",
"ClientId": "guid here",
"TenantId": "guid here",
"Audience": "https://somename.onmicrosoft.com/tenants-api",
"SignUpSignInPolicyId": "B2C_1_RwSignIn"
}
Am I doing something wrong here?
I was able to get this figured out using a different strategy. The recommended configuration in most examples still does not work.
Instance: https://yourname.b2clogin.com
Domain: yourname.onmicrosoft.com
SignUpSignInPolicyId: the actual name of
your signup/sign in policy
ClientId: the client Id of the API Client.
var section = configuration.GetSection("AzureAdClient");
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = $"{section["Instance"]}/{section["Domain"]}/{section["SignUpSignInPolicyId"]}/v2.0/";
options.Audience = $"{section["ClientId"]}";
});
Related
The application in question is a web site which has an API tacked on the side that reuses the many of the data access methods developed for the website. So there maybe some interference between the web site authentication/authorization and the API's. But if that was the case I don't understand why it works locally.
When I run locally, I can test the API using Swagger or Postman to login, get the Bearer token and use it to call the API methods. On Azure, although the login succeeds the next call to the API returns a 401:
www-authenticate: Bearer error="invalid_token",error_description="The signature is invalid"
The most obvious difference is the appsettings.json which I've copied to the Application Configuration blade on Azure:
The original appsettings looked like:
"Rt5": {
"BaseAddress": "https://localhost:44357"
},
"Azure": {
"BuildNumber": ""
},
"AuthentificationJWT": {
"Audience": "ddt-ssp",
"Issuer": "ddt-rt5",
"SecurityKey": "not so secret"
},
Usage - ConfigureServices():
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.AccessDeniedPath = "/Home/Unauthorized";
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthentificationJWT:SecurityKey"])),
ValidateIssuer = true,
ValidIssuer = Configuration["AuthentificationJWT:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["AuthentificationJWT:Audience"],
ValidateLifetime = true,
ValidAlgorithms = new HashSet<string> { SecurityAlgorithms.HmacSha256 },
ValidTypes = new HashSet<string> { "JWT" }
};
});
services.AddAuthorization(o =>
{
o.AddPolicy(AllowViewExceptions.Policy, p
=> p.Requirements.Add(new AllowViewExceptions.Requirement()));
o.AddPolicy(AllowSubmitException.Policy, p
=> p.Requirements.Add(new AllowSubmitException.Requirement()));
});
Usage - Configure():
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseAuthorization();
if (UseSwagger)
{
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); });
}
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Confession: this isn't my code and I'm sure why we have the Cookie stuff mentioned though is does seem to be offer an alternative authorization mechanism. See here:
public string GetAuthorization()
{
//TODO: use ExtractSecurityToken();
var claimToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName);
if (claimToken != null)
{
return $"Bearer {claimToken.Value}";
}
else if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var headerToken))
{
return headerToken;
}
return null;
}
private JwtSecurityToken ExtractSecurityToken()
{
var accessToken = _httpContextAccessor.HttpContext.User.Claims.FirstOrDefault(c => c.Type == AuthenticationConfig.AccessTokenCookieName)?.Value;
if (accessToken == null &&
_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(AuthenticationConfig.AuthorizationHeader, out var value))
{
accessToken = value.ToString().Replace("Bearer ", ""); // TODO: Find a better way then extracting Bearer e.g Get token without scheme
}
if (accessToken == null)
{
return null;
}
return (JwtSecurityToken)new JwtSecurityTokenHandler().ReadToken(accessToken);
}
The problem appears to be with Azure Websites' "Authentication / Authorization" option, which when enabled prevents the Web Api from accepting requests with the Authentication header. Disabling the option and using the Owin library with Azure AD provides the desired solution.
Every app service that is associated with Azure-AD has a corresponding Azure-AD application declaration of type Web app/API. This resource id is the "App ID URI" in the app service's Azure-AD application declaration.
To debug Authentication issues, please refer this msdn link.
It turned out the problem was caused by the bizarre architecture surround the application. The Access Token value (including the SHA256 checksum calculated using the security key) was being generated in another application and passed to this one to use for its communications.
When run localling both applications were using a security key stored in their appsettings.json file and were the same. The pipeline which deployed these applications to Azure was substituting security keys for random values and so they differed.
I've left the question here undeleted because Suryasri's link may help others later.
We have a simple setup. Frontend web application gets a token from Azure AD. Frontend uses that token to authenticate requests to our backend .NET Core Web API. Both will be deployed as Azure Web Apps.
We have found a number of resources on this subject (Microsoft Docs, Blogs, Youtube) however, no matter what we try we are getting 401 Unauthorized errors when we send a request from the frontend to the backend.
Here is our setup:
We have 2 app registrations (one for the frontend, one for the backend)
In the Azure Portal I navigated to Azure AD -> App Registrations -> Backend App -> Expose an API -> Add Scope -> Filled out Form:
Then navigate to the Frontend app registration -> API Permissions -> Add a permission -> Add Access to API exposed in step 2.
Then select "Grant Admin Consent"
Onto the Frontend code:
On the Frontend we are using Microsoft's MSAL library. We have it configured as such:
// Frontend msal config
{
msalConfig: {
auth: {
clientId: "{frontend clientId}",
authority: "https://login.microsoftonline.com/{tenantId}/",
redirectUri: appUri,
postLogoutRedirectUri: appUri,
mainWindowRedirectUri: appUri
},
cache: {
cacheLocation: 'localStorage'
}
}
We send a login request:
// login.js
const request = {
scopes: [
"User.Read",
"api://{Backend Application Id From AzureAD App Registration}/access_as_user"
]
};
instance.loginPopup(request).catch(
(error) => {
console.log(error);
}
);
This works as expected and users are able to login and out of our frontend application.
We place our Azure AD provided token in our Authorization header in requests to our backend API (always returns a 401).
Backend code using Microsoft.Identity.Web (.NET 5):
// .Net appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "{backend clientId}",
"TenantId": "{tenantId}"
}
// Startup.cs
using Microsoft.Identity.Web;
ConfigureServices(IServiceCollection services)
{
//other services
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
//other services
}
Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// other config
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
// other config
}
Any help on how to proceed would be greatly appreciated. We are testing locally right now, but once we get auth working we will be deploying the frontend & backend applications to Azure Web Apps.
UPDATE:
I can see in the 401 response headers that I am getting:
Bearer error="invalid_token", error_description="The signature is invalid"
Try to add "Audience" parameter into AzureAD section: "Audience": "api://{Client_Id}".
To receive more info about auth you can pass subscribeToJwtBearerMiddlewareDiagnosticsEvents: true into services.AddMicrosoftIdentityWebApiAuthentication and check your logs after unsuccessful request.
Also, you can decode(https://jwt.io) your token and check the payload section if there are required scope and audience.
It could be that token (provided to backend) is actually for Microsoft Graph(check appId here: shawntabrizi.com/aad/…). If "scp" is "openid profile User.Read email" and "aud" is "00000003-0000-0000-c000-000000000000" then you are using the wrong token for your backend. Ask Frontend to check the way how they are receiving a token.
Frontend guy put something like this into code, correct me if I am wrong:
public static msalInterceptorConfigFactory(): MsalInterceptorConfiguration {
const resourceMap = new Map<string, Array<string>>();
resourceMap.set(environment.apiUrl, [environment.scope]);
return {
interactionType: InteractionType.Redirect,
resourceMap
};
}
public static msalGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
authRequest: {
scopes: [environment.scope]
}
};
}
# module.ts
providers: [
...
{
provide: MSAL_GUARD_CONFIG,
useFactory: msalGuardConfigFactory
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: msalInterceptorConfigFactory
},
...
],
I have an .net core 5 api that I want to protect using Azure AD, and then call it from a console app. So my case is only service-to-service authentication. I registered both the apps to Azure AD an then gave the permission to the console app on the api using App roles.
I succeeded to retrieve a token for the console app passing the right scope :
static async Task Main(string[] args)
{
var app = ConfidentialClientApplicationBuilder
.Create(<MY_CONSOLE_APP_ID>)
.WithClientSecret(<MY_CONSOLE_APP_CLIENT_SECRET>)
.WithAuthority(new Uri($"https://login.microsoftonline.com/<MY_TENANT_ID>"))
.Build();
var result = await app.AcquireTokenForClient(new String[]
{
"api://<MY_API_APP_ID>/.default"
})
.ExecuteAsync();
}
Then in the Startup class of the API, I added the following code :
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd");
and in the appsettings.json I defined the required settings:
"AzureAd": {
"ClientId": "<MY_API_APP_ID>",
"Instance": "https://login.microsoftonline.com/",
"TenantId": "<MY_TENANT_ID>"
}
Then I marked a controller as [Authorize] but when I call one of its endpoint I get the following error :
{
"code": 401,
"message": "IDX10214: Audience validation failed. Audiences: '[PII is hidden]'. Did not match: validationParameters.ValidAudience: '[PII is hidden]' or validationParameters.ValidAudiences: '[PII is hidden]'."
}
I also tried to prefix the clientId by api:// but got the same error. I decoded the token to ensure the audience was correct and it seems ok since I have "aud": "api://<MY_API_APP_ID>"
Do you have an idea of what I am missing ?
Whatever code provided looks good.
Make sure the Audience config matches the "aud" claim in the access token.
As that part of code is not provided here,
you may be missing an entry of audience in code configuration under services.AddAuthentication(…
Please check if you have given audience entry in any of these ways .
1
.AddJwtBearer(options =>
{
options.Authority = "";
//options.Audience = Configuration["Audience"];
};
(or)
2
.AddJwtBearer(options =>
{
options.Audience = clientId;
options.Authority = authority;
})
(or)
3)
.AddJwtBearer(options =>
{
options.Authority = "";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
//here you give required valid audiences
ValidateIssuer = true,
ValidAudiences = new List<string>
{
"AUDIENCE1",
"AUDIENCE2"
}
or valid audiences can be like below in place of AUDIENCE1 or 2.
ValidAudiences = new List<string>
{
Configuration["Authentication:ClientId"]
}
The aud (audience) should match your API's id, and make sure required scopes are present .If these are fine , then check if the token you are trying to validate is ID token or Access token .It differs for API and graph api.
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 have configured Open ID Connect with Azure AD. I wish to retrieve the access_token from AAD. Currently I am only able to retrieve the id_token. I have configured my Azure Active Directory App registration to include both the access_token and the id_token.
I have configured my Azure Active Directory App registration to include both the access_token and the id_token.
I have also tried retrieving the token from the header without any luck.
Startup.cs
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAD", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = true;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SaveTokens = true;
});
MyController.cs
if(User.Identity.IsAuthenticated)
{
string accessToken = await HttpContext.GetTokenAsync("access_token");
string idToken = await HttpContext.GetTokenAsync("id_token");
}
appsettings.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain",
"TenantId": "organizations",
"ClientId": "myclientid",
"ClientSecret": "myclientsecret",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc"
}
You will need to use CodeIdTokenToken response type, according to the documentation
options.ResponseType = OpenIdConnectResponseType.CodeIdTokenToken;
I managed to fix this. To anyone that would encounter this issue, set the response type to Code to get both the id_token and the access_token. This will instruct Open ID Connect to use the authorization code flow.
options.ResponseType = OpenIdConnectResponseType.Code