I'm currently working on an ASP.net MVC and Web API project with AAD single sign on. The current problem I'm facing is that I don't know how to configure the authorization for the WebAPI. This is my current way I've tried but it doesn't work.
public void ConfigureAuth(IAppBuilder app)
{
...
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "common",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = audience,
ValidateIssuer = false
} });
...}
And I fetch the access token trough postman token predefined token request and that works.
postman token request
When I call the WebAPI (Header: Authorization -> Bearer )I got the following exception back:
{
"Message": "Authorization has been denied for this request."
}
Do I have to prepare the token request, the API header, or the configuration?
BG,
Tom
Here is a sample about Building a multi-tenant web API secured by Azure AD. You could download it and follow the steps it provided to configure it.
The application uses the Active Directory Authentication Library (ADAL) to obtain a JWT access token through the OAuth 2.0 protocol. The access token is sent to the web API to authenticate the user. The web API project demonstrates how to structure your services for being accessed by users coming from multiple Azure AD tenants.
As you want to login multiple tenants, you need to go to the app registered and click Manifest option set availableToOtherTenants to true.
For more details, you could refer to this article.
Related
I am following this official MS doc to implement the OBO flow for two secure Web APIs (let's say Web API 1 and 2) using Azure AD B2C. That previous link points out to the following example on Git.
Basically, I am using the same code:
MyController.cs
string[] scopes = { "profile.read.basic", "user.read" };
UserProfile profile = null;
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenantId);
ClaimsPrincipal principal = HttpContext.User as ClaimsPrincipal;
//Grab the Bearer token from the HTTP Header using the identity bootstrap context. This requires SaveSigninToken to be true at Startup.Auth.cs
var bootstrapContext = principal.Identities.First().BootstrapContext?.ToString();
// Creating a UserAssertion based on the Bearer token sent by TodoListClient request.
//urn:ietf:params:oauth:grant-type:jwt-bearer is the grant_type required when using On Behalf Of flow: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
UserAssertion userAssertion = new UserAssertion(bootstrapContext, "urn:ietf:params:oauth:grant-type:jwt-bearer");
// Creating a ConfidentialClientApplication using the Build pattern (https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-Applications)
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.WithClientSecret(appKey)
.WithRedirectUri(redirectUri)
.Build();
// Acquiring an AuthenticationResult for the scope user.read, impersonating the user represented by userAssertion, using the OBO flow
AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
On StartUp.cs I had to set up the SaveSigninToken to true
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.TokenValidationParameters = new TokenValidationParameters { SaveSigninToken = true };
}, options => { Configuration.Bind("AzureAdB2C", options); });
When I run the Web API using Swagger and hit the tested endpoint the following line of code:
AuthenticationResult result = await app.AcquireTokenOnBehalfOf(scopes, userAssertion).ExecuteAsync();
thrown the following error:
AADSTS50013: Assertion failed signature validation. [Reason - The key was not found.]
Trace ID: c0d53284-12f3-4ab0-a42c-d7c35e2ad300
Correlation ID: e37849e8-938b-441e-bd80-d1612733dc17
Timestamp: 2021-08-20 22:13:53Z
From Azure AD B2C I have granted permission to Web API 1 from the App Registration to communicate to the Web API 2.
For the given error, I have been doing some research/ investigation as well as trying a few different approaches but no luck.
Does any one know how to troubleshoot this?
Thanks in advance
If these really are tokens issued via Azure AD B2C endpoint, then it simply is not compatible.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/custom-policy-developer-notes#oauth-20-application-authorization-flows
Your error code, however, shows you are trying to use the Azure AD endpoint, probably of your Azure AD B2C tenant.
In which case, you seem to be using an Azure AD B2C signed token, and trying to do OBO against the Azure AD endpoint, and both use different signing keys. This therefore will not work.
Only the Azure AD endpoint will do OBO flow. I.e. Users tokens issued from Azure AD token endpoint, and performing OBO against the same endpoint with the users AAD token.
AAD endpoint: https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token
AAD B2C endpoint: https://contoso.b2clogin.com/<tenant>/<B2C-policy-id>/oauth2/v2.0/token
You can't use these two endpoints together for different things.
I have 2 APIs and I wanted to use OAuth 2.0 On-Behalf-Of flow to generate the token in API1 and Authorize to API2 with the generated token.
I followed https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#middle-tier-access-token-request and I was able to generate the token.
I use this following parameters:
grant_type:urn:ietf:params:oauth:grant-type:jwt-bearer
client_id:xxxxx
client_secret:xxxxxx
requested_token_use:on_behalf_of
scope:https://graph.microsoft.com/user.read
But I don't know how to authenticate to my API2 with that token and get the user and what are the next steps. My API to uses .netframework app.
I created OwinStartup and added UseWindowsAzureActiveDirectoryBearerAuthentication
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions()
{
Tenant = "xxxxxxxxxx",// Azure tenant Id
TokenValidationParameters = new TokenValidationParameters()
{
SaveSigninToken = true,
ValidAudience = "https://graph.microsoft.com"
}
});
and added Authorize attribute into my controller. but when I called the get endpoint to that controller, it gave me an error saying:
Authorization has been denied for this request.
You need to request two tokens, one is the access token of API A, and the other is the access token of API B obtained using the access token of API A as a parameter.
The document you refer to is based on the api A access token you have obtained, and there are instructions in the document.
First, you need to use the auth code flow or other login flow to obtain the access token of api A:
Then use the access token of API A as a parameter to obtain the access token of API B.
In API A I was using a wrong scope to generate token. https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#middle-tier-access-token-request
I'm working with a .Net Core Web API and a .Net Core Web MVC Application. They both use Azure AD B2C to authenticate users. But in order to get a response from a HttpRequest from the Web API I need to provide the JWT Access Token from B2C for the signed in user on my web MVC application. Is there a way to obtain this access token inside a controller using the authenticated "User".
I have tried accessing the claims of the signed in user but no luck there, I have also used jwt.ms to review that the B2C workflow works well and that the JWT token is being generated and it works as well. The MVC application is authenticating the user and the web API is working fine with a hardcoded token. I just need to obtain the access token from a signed in user rather than doing it hardcoded.
I expect to be able to get the B2C JWT access token so that I can later on pass it to the Web Api and be able to secure my requests.
After getting some help from the MS AzureADB2C.UI GitHub crew we were able to solve the issue. The issue was that the tokens aren't saved by default on the library, so we needed to configure OIDC to specify that the tokens have to be saved for future use within the application. And so here is the example code of the "Startup" configuration and the example of how to query the "JWT access token" from the controller.
Startup.cs:
services.Configure(AzureADB2CDefaults.OpenIdScheme, options => {
options.SaveTokens = true;
});
Controller:
string idToken = await HttpContext.GetTokenAsync("id_token");
More information on how was the issue solved can be found on the following link:
https://github.com/aspnet/AspNetCore/issues/11424
You can refer to this sample application.
It uses the ASP.NET Core Azure AD B2C middleware to authenticate the end user and MSAL.NET to acquire, cache, and refresh the access token.
The access token is acquired in the AzureADB2COpenIdConnectOptionsConfigurator class.
A code example for a controller method referencing the access token is here.
Is it the actual token string you need? If so, you can access the headers using the HttpContext within the controller? The HttpContext will have a collection of headers that were passed in
I'm having hard times trying to use Azure AD B2C to authenticate My Web API.
I'll start with some background
I created mobile application which is using Azure AD B2C to authenticate users. I'm creating a WebView which display this url:
User is asked to login to azure ad, if the login data is successfull i'm receiving a response containing the access token - this part went smooth, everything works properly.
Now i want to create backend Web Api. I created ASP NET Core Web application which allows me to choose authentication method. I choose the Azure AD authentication so the template generated all required data for me. The relevant part in the code is here:
I updated all required config properties to match my azure settings. At this point i would expect to be able to call the API using access token i received on the mobile app. I run mobile app locally, signed in, received access token, copied it and tried to call my web api(hosted in IIS express) using postman ( with authorization header "Bearer ..." ). Unfortunately with no luck - i'm receiving 401 with following header:
Bearer error="invalid_token", error_description="The signature key was
not found"
I thought token is enough to Authorize the API - i understand this is a whole point of OAuth. Am i missing something ? Should i have some additional config ? I Noticed the config is missing the sign in policy ( which seems to be required by AD B2C name so i tried adding that:
var validationParameters = new TokenValidationParameters
{
AuthenticationType = "MY_POLICY",
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
Audience = Configuration["Authentication:AzureAd:Audience"],
TokenValidationParameters = validationParameters
});
But this didn't work too. Will appreciate any help.
EDIT
I found following error in Visual Studio logs:
Bearer was not authenticated. Failure message: IDX10501: Signature
validation failed. Unable to match 'kid': '...'
#juunas comment help me to find the issue. I inspected outgoing requests with fiddler and i found that with this piece of code:
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"]
The request was being send to following address:
https://login.microsoftonline.com/MYTENANTID/.well-known/openid-configuration
There are two issues with above:
It's not using v2 endpoint. Proper link for B2C should always use v2 so it would look like:
https://login.microsoftonline.com/MYTENANTID/v2.0/.well-known/openid-configuration
It was not adding sign in policy to the link ( even if i set it in token options )
I managed to make it work with removing "Authority" parameter and changing the configure auth function to following:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
MetadataAddress = string.Format("https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}",
Configuration["Authentication:AzureAd:TenantId"], "MYPOLICY"),
AuthenticationScheme = "MYPOLICY",
Audience = Configuration["Authentication:AzureAD:ClientId"],
});
I have an Owin based Web App and a backend Web API, they are authenticated against AAD and the workflow can be describe as below listed.
Web App authenticates end users against AAD using Federation Authentication.
Web App requests a JWT from AAD for accessing the backend Web API.
The main code for authenticating end users.
public void ConfigureAuth(IAppBuilder app)
{
// other code...
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
Wtrealm = realm,
MetadataAddress = adfsMetadata
});
}
The main code for getting JWT for accessing the backend API:
internal async Task<string> GetAccessToken()
{
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(apiId, credential);
// Here, what I wanted is to use the other overloaded method
// authContext.AcquireTokenAsync(apiId, credential, userAssertion);
// But to instantiate a UserAssertion instance, the only way is
// to use the constructor new UserAssertion(assertionString)
// and the assertionString should be in JWT format
// unfortunately, the assertionString from Ws-Federation auth is
// for sure in SAML2 format. So, the question is:
// Give I am using Ws-Federation auth protocal, How can I pass the
// user information in requesting a JWT to backend API resource?
return result.AccessToken;
}
Generally, the whole authentication workflow is OK, I can both authenticate end users and get JWT for accessing backedn APIs. But the problem is that there is no end user claims in the JWT. I am sure I should get users claims from the federation authentication result and then put them in the process of requesting the JWT. Unfortunately, with all methods, libraries and classes I didn't find a solution to do that.
BTW, https://github.com/Azure-Samples/active-directory-dotnet-webapp-webapi-openidconnect gives an example how to obtain a JWT with end user claims included, but the solution does not work with my scenario as I am using Federation authentication rather than OpenID Connect.
Edit
To make the question clear: in the web app, I would like to request a JWT token for accessing the backend web api by using the method AuthenticationContext.AcquireTokenAsync.
From my demo code, you can see I am using the AcquireTokenAsync(apiId, clientCredential) overloaded verion. But this version does not attach the end users claims inside. Actually what I needed is the AcquireTokenAsync(apiId, clientCredential, userAssertion) overloaded method.
However, to instantiate a UserAssertion, I need the user assertion string which is the AccessToken from user authentication result. Unfortunetaly, the UserAssertion class only accept JWT format assertion string, but the Ws-Federation authentication returns the SAML2 format assertion string, so I am not able to instantiate a UserAssertion instance.
So, my question is: given the condition that I am using Ws-Federation authentication protocol for authenticating an end user, in the backend how can I pass the user assertion information (it is in SAML2 format) to AAD for requesting a JWT for a backend api resource?
AAD provides "canned" claims. There are no claims rules to add other attributes to the token.
Refer: Supported Token and Claim Types.
If you want other attributes, you need to use the Graph API.