I have setup MSAL to fetch tokens from Azure AD B2C, setup dotnet core WebAPI to accept JWT tokens. Pointed WebApi at the Authority Endpoint:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtOptions =>
{
string tenant = Configuration["AzureAdB2C:Tenant"], policy = Configuration["AzureAdB2C:Policy"], clientId = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{tenant}/{policy}/v2.0/";
jwtOptions.Audience = clientId;
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
as per the samples. MSAL is configured to use the same policy and same client Id and receives token.
MSAL Authority - https://login.microsoftonline.com/tfp/{tenant}.onmicrosoft.com/{policy}/v2.0.
However, that AuthFailed event handler just returns
IDX10501: Signature validation failed. Unable to match keys.
and bounces the auth as a 401.
I went looking for signing keys and the kid of the token is not the same as the kid listed at the discovery endpoint.
https://login.microsoftonline.com/tfp/{tenant}/{policy}/discovery/v2.0/keys
Any ideas?
Seems that I had not selected the correct Issuer claim setting. MSAL was grabbing its token using the https://login.microsoftonline.com/{guid}/v2.0 endpoint whereas WebAPI was using the https://login.microsoftonline.com/tfp/{guid}/{policy}/v2.0/ issuer.
As per the docs this isn't an openid compatible endpoint, but works fine for B2C. Pays to check over the two different claim sets!
Related
The docs for the OAuth2 auth code to token exchange show making a request using client_id and client_secret. However is there a way to do this using cert based auth for the azure app?
Yes you can acquire tokens using a certificate instead of using client secret as well. It's covered as part of Client Credentials Grant.
Azure AD V1 Endpoint
Here is a detailed code sample - It makes use of a self signed certificate and uses Azure AD V1 endpoint
Authenticating to Azure AD in daemon apps with certificates
certCred = new ClientAssertionCertificate(clientId, cert);
result = await authContext.AcquireTokenAsync(todoListResourceId, certCred);
In case you're looking to make direct REST based calls (not using ADAL Library) here's a sample. You can read more details on each of the parameters here on Microsoft Docs:
Access token request with a certificate
POST /<tenant_id>/oauth2/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
resource=https%3A%2F%contoso.onmicrosoft.com%2Ffc7664b4-cdd6-43e1-9365-c2e1c4e1b3bf&client_id=97e0a5b7-d745-40b6-94fe-5f77d35c6e05&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg&grant_type=client_credentials
Azure AD V2 Endpoint
Using MSAL.NET Library you can do it like this. Both client secret and Certificate Credentials variations are shown here. (Certificate is covered in the else case)
More details available here - Client credential flows in MSAL.NET
// Even if this is a console application here, a daemon application is a confidential client application
IConfidentialClientApplication app;
#if !VariationWithCertificateCredentials
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithClientSecret(config.ClientSecret)
.Build();
#else
// Building the client credentials from a certificate
X509Certificate2 certificate = ReadCertificate(config.CertificateName);
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithTenantId("{tenantID}")
.WithCertificate(certificate)
.Build();
#endif
// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = null;
try
{
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
}
catch(MsalServiceException ex)
{
// Case when ex.Message contains:
// AADSTS70011 Invalid scope. The scope has to be of the form "https://resourceUrl/.default"
// Mitigation: change the scope to be as expected
}
Again in case you're interested in making direct REST based calss (not using MSAL Library) here's a sample.You can read more details on each of the parameters here on Microsoft Docs:
Access token request with a certificate
POST /{tenant}/oauth2/v2.0/token HTTP/1.1 // Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&client_id=97e0a5b7-d745-40b6-94fe-5f77d35c6e05
&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg
&grant_type=client_credentials
In JWT from AAD there is a key 'aud'. https://jwt.io/, says it is 'Audience. (Who or what the the token is intended for)'. My question is, Are aud values website specific - can I check the aud and expect it to be same to check if the token is intended for my specific site?
In Azure AD, the audience value always indicates the resource the token is targeted on.
You can acquire an access token by using either the API's client id or Application ID URI.
What you use will be the audience in the token.
So if you make an API, you should check the audience is either the API's client id or Application ID URI.
You can know for sure it will always be one of those if the token is meant for your API.
EDIT: The below information is not correct.
If I know your API's identifier + your tenant id,
I can acquire an access token for your API using client credentials!
The token will not contain scopes or roles, it cannot.
So it is critical that you check for the presence of valid delegated permissions (aka scopes) or valid app permissions (in roles claim).
THIS IS WRONG: If I tried to acquire an access token using your API's identifier from my AAD tenant, it would not give me a token.
Any app that passes an access token with the correct audience had rights to call your API when it acquired the token.
You already got a good explanation of the audience value from juunas.
I'm adding here a specific code example from Azure-Samples on Github which shows how to validate the JWT Token manually and checks among other things audience value. (It's pretty important to validate issuer as well)
Look at this particular code and especially near the comment "We accept both the App Id URI and the AppId of this service application"
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// We accept both the App Id URI and the AppId of this service application
ValidAudiences = new[] { audience, clientId },
// Supports both the Azure AD V1 and V2 endpoint
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
IssuerSigningKeys = signingKeys
};
Code Sample:
Specific file with code excerpt shown above
Azure-Samples: Manually validating a JWT access token in a web API
I already have a AAD integrated WebAPP using ASP.Net Core 2.1 but now i want to develop an API using ASPNet Core 2.1 to authenticate AAD user to my api using JWT bearer token. I am unable to perform the same because in web app i am using Cookie Auth mode but here i need to implement JWT Bearer which was not working for me. I tried a lot of code from different code repo.
References:
https://github.com/juunas11/Joonasw.AzureAdApiSample
https://github.com/Azure-Samples/active-directory-b2c-dotnetcore-webapi
https://azure.microsoft.com/en-in/resources/samples/active-directory-dotnet-native-aspnetcore-v2/
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "https://localhost:44395/";
options.Authority = "https://localhost:44395/identity/";
})
.AddJwtBearer("AzureAD", options =>
{
options.Audience = "https://localhost:44395/";
options.Authority = "https://login.microsoftonline.com/tenantID/";
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
"AzureAD");
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
When i am changing the mode to Cookie Mode it is working fine but not working in JWTBearer code.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer, AzureAD).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: Bearer was challenged.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: AzureAD was challenged.
Could anyone help me here? I am stuck here due to this issue.
Thanks in advance
Suppose you have REST API resources written in ASP.NET Core Web API which protected by Azure AD , the client(ASP.NET Web application) can use the OpenID Connect middleware and the Active Directory Authentication Library (ADAL.NET) to obtain a JWT bearer token for the signed-in user using the OAuth 2.0 protocol.
The bearer token is passed to the web API, which validates the token and authorizes the user using the JWT bearer authentication middleware, for example , refer to code sample in first link :
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
o.Authority = Configuration["Authentication:Authority"];
o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
// Both App ID URI and client id are valid audiences in the access token
ValidAudiences = new List<string>
{
Configuration["Authentication:AppIdUri"],
Configuration["Authentication:ClientId"]
}
};
});
Above code sample use JwtBearerExtensions to validate the access token . You could click here for explanation about scenario.
I am stuck with Azure Active Directory OpenId.
I Have a ReactJs Web Client that does Authentication to Azure AD, When signed in a token_id is return representing a JWT token. This will be passed back to a ASP.Net Core Web Api and then Authenticated with the JwtSecurityTokenHandler in System.IdentityModel.Tokens.Jwt.
This is wired up with the Middelware when the application starts. When you access the OpenIdConnectOption Events on TokenValidated call, the token is validated already in the back end. A TokenValidatedContext get passed in to the Event Method which contains the JwtSecurityToken and I have access to the whole JWT Token.
I created a separate validation service that takes in the token string.
public ClaimsPrincipal ValidateToken(string token)
{
SecurityToken validatedToken;
var sectoken = TokenHandler.ReadJwtToken(token);
var tokenValidationParams = new TokenValidationParameters()
{
ValidAudiences = sectoken.Audiences,
ValidIssuer = sectoken.Issuer,
};
return TokenHandler.ValidateToken(token, tokenValidationParams, out validatedToken);
}
Is that the SigningKey object is null and the ValidateToken fails. If I do the same validation in the Event call then the SigningKey is valid and the cert is set to Microsoft Cert that seems to be issues by the Azure AD Service.
The main objective is to have a separate Token Validation Service.
Thanks in advance.
The sample app can be found here --> https://github.com/Azure-Samples/active-directory-dotnet-webapp-wsfederation.
The application is using Microsoft.Owin and this is what I am expecting:
Users navigate to your application.
Your application redirects anonymous users to authenticate at Azure AD, sending a WS-Federation protocol request that indicates the application URI for the realm parameter. The URI should match the App ID URI shown in the single sign-on settings.
The request is sent to your tenant WS-Federation endpoint, for example: https://login.windows.net/solexpaad.onmicrosoft.com/wsfed
The user is presented with a login page, unless he or she already has a valid cookie for the Azure AD tenant.
When authenticated, a SAML token is returned in the HTTP POST to the application URL with a WS-Federation response. The URL to use is specified in the single sign-on settings as the Reply URL.
The application processes this response, verifies the token is signed by a trusted issuer (Azure AD), and confirms that the token is still valid.
My Question:
After the authentication a SAML token is returned via the HTTP POST. How can I view the SAML response? Currently when I view the HttpContext after POST there is nothing in it.
Thanks for any help.
In the App_Start/Startup.Auth.cs, you should be able to get access to the token.
I've added the SecurityTokenReceived Func:
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = metadata,
Notifications = new WsFederationAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("Home/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenReceived = context =>
{
// Get the token
var token = context.ProtocolMessage.GetToken();
return Task.FromResult(0);
}
}
});