I obtain an access_token and id_token from AzureAD for my app, which is using OAuth2 with the implicit flow. This is a sample URL where I obtain the tokens:
https://login.microsoftonline.com/my_tenant_id/oauth2/v2.0/authorize?response_type=id_token+token&client_id=my_client_id&state=some_state&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fsign-in&scope=openid%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read&nonce=some_nonce
The scope is openid https://grap.microsoft.com/user.read.
The response_type is id_token+token.
I also have a Asp.Net backend, I want to secure. So I use the Authorize Attribute for my controller and send a token in the header as like this: "Authentication : Bearer THE_TOKEN".
My configuration in Startup.cs looks like this:
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = string.Format("https://login.microsoftonline.com/{0}/v2.0/",
"d67853c3-db96-4dac-a37b-f2bfb12b42d1"),
Audience = "8422b3fb-5612-4fdd-a90f-707d7218de57"
});
From what I have read, the access token should be used for this, and the id_token should not leave the frontend. But authentication in the backend only works with the id token in my case. The access_token can not be signed Bearer error="invalid_token", error_description="The signature is invalid".
Looking at the access_token in jwt.io, I see the tokens have different audiences and issuers. The access_token for example has this
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/d67853c3-db96-4dac-a37b-f2bfb12b42d1/",
whereas the id token has this
"aud": "my_client_id",
"iss": "https://login.microsoftonline.com/my_tenant_id/v2.0",
So it seems to me, the access_token was somehow issued for the Graph API. Would be glad if someone could tell me, what i am doing wrong or how I can try to solve my issues.
edit:
It WAS working as intended before when I used the scope openid profile. But due to changes in Azure, this scope is not valid anymore and Microsoft directed me to use the scope mentioned above.
As you mentioned, the access token you requested is for the Microsoft Graph. And the id_token is only for the client to authenticate the user instead of for the resource server.
To protect the web API using the Azure AD V2.0 endpoint, we can acquire the access token for the web API like request below:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=token&client_id={client_id}&scope=api://{client_id}/access_as_user&redirect_uri={redirect_uri}
And here is the code using protecting the web API via Azure AD V2.0 endpoint:
public void ConfigureAuth(IAppBuilder app)
{
System.Diagnostics.Trace.TraceWarning("Hello");
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = clientId,
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}
}
More detail about protecting the web API via Azure AD V2.0 endpoint, you can refer the document below:
Calling a web API from a .NET web app
Related
I am trying to make login with JWT authentication. But wherever i find the way of use it always use with api but I do not want to use api i just want to creat just separate controller for login.Kindly tell me how to authorize with jwt and without api.
According to your description,if you want to enable the JWT authentication for MVC, I suggest you could follow this article.
The difference between the JWT authentication for the MVC and Web API is you need store the token at somewhere, since the web api you need to set the token inside the Authorization header for each request.
To solve this issue, you could follow the article which I shared, it store the token inside the session and then it will write a middleware which will read the token from session and then set it at the request header.
The middleware like this.
app.Use(async (context, next) =>
{
var token = context.Session.GetString("Token");
if (!string.IsNullOrEmpty(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
await next();
});
For store the token, you could write you own codes and then using the session storage to store the token.
HttpContext.Session.SetString("Token", generatedToken);
I have simply basic solution for asp.net core web app and Is4.
In asp.net core I use addCookies() method, but the is4 returns me jwt token, why there is jwt when I use cookies?
I figure out it when I call
HttpContext.getTokenAsync(openIdconnectparameterNames.idToken)
Here I get jwt.. Can someone explain me why I get JWT if I'm using cookies?
I will try to explain how authentication using OpenId Connect Authorization Code flow is usually implemented for an asp.net core web app.
When an unauthenticated user (no cookie) tries to access your web app, the web app initiates the authorization code flow in order to authenticate the user. During the flow, the user is prompted to log in and then at some point, if login was successful, the identity provider (IdentityServer4) sends an ID token and an access token to the web app as you can also see in the diagram below:
The ID token contains user profile information. This is how the web app knows which user logged in. The access token can be used to get additional user information by calling the User Info Endpoint of Identity Server. Now the web app creates a cookie that contain all necessary user information, so that the next time he tries to access the web app, it won't have to initiate the whole authorization flow again. The web app will be able to identify if user is authenticated by validating the cookie.
The whole id token and access token are saved inside the cookie if you set SaveToken property to true in AddOpenIdConnect options.
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
//...
// access token is needed to make this request to identity server
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
Then you can read the tokens from cookie using
var accessToken = await HttpContext.GetTokenAsync("access_token");
var idToken = await HttpContext.GetTokenAsync("id_token");
Saving the tokens in the cookie is not necessary and you shouldn't do it if you don't need the tokens anymore after authentication (for example you might need the access token to access an API: https://docs.identityserver.io/en/latest/quickstarts/3_aspnetcore_and_apis.html).
https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow
I'm implementing Azure AD B2C in a new ASP.NET Core 2.1 app.
I've already created the Azure AD B2C tenant and registered my app, etc.
After I login, I get redirected to the URL I specify and I see the token in the URL but I get an error stating the app requires authentication -- see below:
I saw a few similar posts and what I gather is that the token is automatically validated by the middleware. Is that not so?
What do I need to do at this point?
The code I included in my app are as follows:
In ConfigureServices() method:
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
In Configure() method in Startup.cs, all I have is app.UseAuthentication();
In my controller, I'm also using [Authorize] to make sure my actions are not open to anonymous users.
What am I missing? How do validate the token and get the claims?
Combining our discussion here as an answer.
The typical approach to this kind of app is that you allow unauthenticated clients to download the HTML, JS, and other static content.
Then the front-end can use MSAL.JS to authenticate the user.
The front-end SPA will get an Id token which tells the front-end who the user is.
MSAL.JS also allows you to get access tokens to call APIs.
It uses hidden iframes + the Implicit Grant flow to do this.
That access token will then need to be attached to requests to the API as a header (Authorization: Bearer token-goes-here).
MSAL.JS will use session or local storage to store the tokens (this is configurable).
So no cookies are used in this setup.
Then the back-end API should authenticate the access token it receives in the header.
What you have there is already sufficient to authenticate the token.
services.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions => {
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
The JWT Bearer authentication handler will load the OpenId Connect metadata document on startup from the authority configured here.
That allows it to get the B2C tenant's public signing keys among other things.
This info allows the handler to then validate access tokens as they come in without interacting with B2C in any way.
It checks the signature is valid, the issuer is valid, and that the audience in the token is what has been configured.
Authorization is of course not handled by the authentication handler, so you must then also check that the calling user actually is allowed to access the resource they are accessing.
The user id is available in the access token.
MSAL.JS may have done some validation on the token as well (I can't remember right now if it did), but doing validation in the front-end is something that can be worked around by anyone with control of the user's browser.
Validation on the API side is the most important piece.
There are two ASP.NET Core Web APIs that use bearer token authentication. The services also serve static files which are used to consent the APIs into the directory of a consuming application. So both APIs have implicit flow enabled in Azure AD.
API2 is calling into API1 from the Web API controller using the jwt-bearer grant. API2 has permission to access API1.
A user from a third directory navigates to the SPA served by API2. The user is redirected to Azure AD, signs in and consents the API. The user is redirected back to the SPA application and an AJAX call is made to the API2 web API. From that controller, another call is made to API1. This call is authenticated using the jwt-bearer grant (urn:ietf:params:oauth:grant-type:jwt-bearer).
When the AcquireToken call is made with client credentials for API2 and the JWT token used to call into API2, Azure AD responds with an error:
Microsoft.IdentityModel.Clients.ActiveDirectory.AdalServiceException:
AADSTS50027: Invalid JWT token. AADSTS50027: Invalid JWT token. Token format not valid.
Trace ID: 4031717e-aa0c-4432-bbd1-b97a738d3e6f
Correlation ID: 61ae6cd6-6df6-49ee-9145-c16570c28f7b
Timestamp: 2017-02-13 22:44:01Z ---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (BadRequest). ---> System.Exception: {"error":"invalid_request","error_description":"AADSTS50027: Invalid JWT token. AADSTS50027: Invalid JWT token. Token format not valid.\r\nTrace ID: 4031717e-aa0c-4432-bbd1-b97a738d3e6f\r\nCorrelation ID: 61ae6cd6-6df6-49ee-9145-c16570c28f7b\r\nTimestamp: 2017-02-13 22:44:01Z","error_codes":[50027,50027],"timestamp":"2017-02-13 22:44:01Z","trace_id":"4031717e-aa0c-4432-bbd1-b97a738d3e6f","correlation_id":"61ae6cd6-6df6-49ee-9145-c16570c28f7b"}
Can anyone tell me what this error means? The JWT token itself is correct, it must be one of the claims inside that Azure AD dislikes.
I have the sample apps and repro steps in this github repo.
EDIT
A diagram may clarify what I'm trying to do. It's the interaction #3 that is giving me the error. It uses a ClientCredential with ClientId of API2 and a ClientSecret (or key) and the orange JWT token issued by directory3 with an audience of API2.
The orange JWT token looks like:
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Il9VZ3FYR190TUxkdVNKMVQ4Y2FIeFU3Y090YyIsImtpZCI6Il9VZ3FYR190TUxkdVNKMVQ4Y2FIeFU3Y090YyJ9.eyJhdWQiOiJmOTJjNGI5MS05NTY3LTRjYjgtOTI4MC0yYmFjNDUyYmZjZTEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9iYWRiODcyNC1jODExLTRlYjEtOTcwZi04YmI4MzU0NTI0OTEvIiwiaWF0IjoxNDg3MDM1MDI5LCJuYmYiOjE0ODcwMzUwMjksImV4cCI6MTQ4NzAzODkyOSwiYW1yIjpbInB3ZCJdLCJmYW1pbHlfbmFtZSI6Im9uZSIsImdpdmVuX25hbWUiOiJ1c2VyIiwiaXBhZGRyIjoiNzMuMTU3LjExMC4xNjQiLCJuYW1lIjoidXNlciBvbmUiLCJub25jZSI6ImRjOTYwMjkzLWQ0MTItNDFmNy1iMGRhLWYzZWM2NTE1ZTM1YSIsIm9pZCI6IjUzMTE1OTY0LWQwOTMtNGM5NS05MDkzLTg0ZjliNzVmYzNlOSIsInBsYXRmIjoiMyIsInN1YiI6IlVjVFVleGJYd1BWYkZ4aGRDUW9MR25vTkdsZnVQWi1feGtaSndIdU9zM1EiLCJ0aWQiOiJiYWRiODcyNC1jODExLTRlYjEtOTcwZi04YmI4MzU0NTI0OTEiLCJ1bmlxdWVfbmFtZSI6InVzZXIxQHppZWtlbmh1aXNBLm9ubWljcm9zb2Z0LmNvbSIsInVwbiI6InVzZXIxQHppZWtlbmh1aXNBLm9ubWljcm9zb2Z0LmNvbSIsInZlciI6IjEuMCJ9.lhwEL3x3Cu66l-Dt-hWmH2DrmCCX2YORGhl4x4_13_lZuUVhMr1OFLUdJ4MZRWG5DJMc8F_SyC4XdDiStwFDaLSI_4L6noXNau3KF6Su3PnqD-FoXoQPtmPPmFrDRZ7nPEtSazEcd9HeSwgVvRZywJRBKQqQQtBGBpS7-Y9kxrO-moUhnBdJJ-gwhu_wxwdEZaOuLs68sZuFaONAunElMKO1iYlC9VHP5xrVzh3ErnRSIp3xmgJNmlbf-9AFUSrjN5UaFjfpGGGJIvoaKbL6rq-J1XNpvKZDFYvoC7RMkqS1KM-lu-EI7-QCksb3NKhTg6J_bz5uxmjYltjKanWbUg
First, to enable the API2 could onbehalfof the user to acquire the token for API1, we need to grant the API1 to API2 when we register the API2 in the Directory2 like below:
Then since the service principal of API1 is also not created in Directory3, we need to sign-in the API1 first to to create the service principal of API1 on Directory3 tenant.
When the users sign-in the SPA from Directory3 and send the request to the API2, in the code you were acquire the token from Directory2. We should acquire the token from the tenant of users(Directory3).
And in the code, you were acquiring the token using the clientId, in this secnario, we should use the ClientCredential like below:
var result = await adal.AcquireTokenAsync(resource, clientCred, userAssertion);
After changing, the code works well for me. Please let me know whether it helps.
In addition, the scenario you are developing is like below and you can refer the detail from this link.
I have an application let's name it 'Apple' which is registered with Azure AD having delegated rights on Azure Management API application. When requested to this application it creates azure resource ex. storage account automatically and this works fine.
I have another application which is MVC application and it's also registered with same AD tenant. The second application uses following code for retrieving access token:
var clientCredentials = new ClientCredential(ConfigurationManager.AppSettings["AD_ClientID"], ConfigurationManager.AppSettings["AD_Client_AccessKey"]);
var authContext = new AuthenticationContext(string.Format(ConfigurationManager.AppSettings["AD_Tenant_Login_Url"], ConfigurationManager.AppSettings["AD_Tenant_Id"]));
var result = authContext.AcquireTokenAsync(ConfigurationManager.AppSettings["AD_Resource"], clientCredentials);
if (result == null)
{
throw new InvalidOperationException("Could not get the token");
}
return result.Result;
The result is an access token having different properties. Now second application, retrives access token with access to resource apple, which it then passes to Apple application in authorization header.
Authorization:bearer TokenString
The Apple application is having Authorize attribute added to controller.
The application is configured with Owin with oauth application with following code
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
Please note that the access token is retrieved from second application using its own AppId and Secret key; whereas the other(Apple) application uses its own AppId and secret key for validating the token.
So my problem is, the APPLE application always returns 401 not authorize code
To above, question, the answer was, Resource ID (during token request) and Audience Id (during validation of token in second application) were not matching. Keeping those same solved the problem.
Then I ran into another issue, which I have described here
It seems, If I work with newer Azure Portal (which is still in preview version), the AD token does not include "Roles" field in JWT token. If I follow same procedure in Older Portal for configuring apps, then AD includes "Roles" field in JWT token and scenario executes as expected.
I should avoid using Azure new portal for preview features at least!