I am trying to implement an architecture formed by
.NET Core MVC client
Auth Server with OpenIdDict
Regarding the Auth Server I should have implemented a Resource Owner Password Credentials Grant: the token endpoint authenticates the user, creates a principal and releases
access_token
refresh_token
id_token
each with claims with appropriately set destinations
The problem is the client. Wanting to implement a ROPC client, the web application must
call the token endpoint
fetch the access_token
Build a Principal cookie with related claims
How can I build a cookie with claims inside?
How do I recover the claims? The access_token is encrypted. How can I use the id_token to retrieve the claims and put them inside the cookie?
Edit:
Here the code that generate the tokens
if (request.IsPasswordGrantType())
{
var user = await _userService.FindUserByUserNameOrEmail(request.Username);
if (user == null)
return GetForbidResponse("Account non trovato.");
// Validate the username/password parameters
bool pwdCheck = await _userService.CheckUserPassword(user, request.Password);
if (!pwdCheck)
return GetForbidResponse("Password non valida.");
//Create the principal
var principal = CreateUserPrincipal(user);
//Set Scopes
SetPrincipalScopes(principal, request);
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
And this is an example of response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhGN0EwMTI2Nzc3QzkxMDE5QTk2NEE2NDhERUE0RTAwOUQ5QkNFRUMiLCJ4NXQiOiJqM29CSm5kOGtRR2Fsa3BramVwT0FKMmJ6dXciLCJ0eXAiOiJhdCtqd3QifQ.eyJuYW1lIjoicHN5Y2hvODciLCJlbWFpbCI6ImRhdmlkZS5tYWdnaXVsbGlAZ21haWwuY29tIiwic3ViIjoiMTk1MiIsInJvbGUiOlsiVXNlciIsIlN5c0FkbWluIl0sIm9pX2F1X2lkIjoiNiIsIm9pX3Rrbl9pZCI6IjI2Iiwic2NvcGUiOiJvcGVuaWQgb2ZmbGluZV9hY2Nlc3MiLCJleHAiOjE2NDgxNDgzMjgsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvIiwiaWF0IjoxNjQ4MTQ0NzI4fQ.nCcIbLpYDCNi093uPYHH1KA7-1StRm8G4QLRp_MeEO3ixHP2syoL_nSmkwUrv2_NHrNLzbzTgn4SSaKZTVvKHbOXs89s2S3-vJD62DnB_bc2M--z2V-4txerwTK4Q1zGwV28YtJgG_lLR-KBYvi4DQPqBH0FDAy9DF72P7gmEb_xkeUTmSvzQ3MklyuYNK6youI-y5R0x_0QEFWwkpGYnHTEIj_RmaEvz3Nz3Ic_vhwOnjCOMpAE7KLO7cYT2xFiNKwbIDfsnmHnzaFhUMdzpPsvk-VyGTu35kB4ME6mQnPObWktzncdZu1wXVoYYs3NQdRzTxxJkC0Zw0YPr4jpUA",
"token_type": "Bearer",
"expires_in": 3599,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhGN0EwMTI2Nzc3QzkxMDE5QTk2NEE2NDhERUE0RTAwOUQ5QkNFRUMiLCJ4NXQiOiJqM29CSm5kOGtRR2Fsa3BramVwT0FKMmJ6dXciLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiIxOTUyIiwib2lfYXVfaWQiOiI2IiwiYXRfaGFzaCI6ImNrN2dSdnhBcnZlN3k2emd0ZWZlZ3ciLCJvaV90a25faWQiOiIyOCIsImV4cCI6MTY0ODE0NTkyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMS8iLCJpYXQiOjE2NDgxNDQ3Mjh9.iFsE4eWiSfvpzTRHFWCtX-HiHXaWJ9UO7cQdTT2u-Md87jLTSB66mmBECLdYWEYFKCi-RyX3ZKAjpoW5Le2OeFsE-wqXxDaEeCIQ9t0nz1lVzdJVHq4RcaiwR1nSEkmOkAD74OzPGj7Vj3BOppUxtXHQG4ZAFCk9CxCQD2yfMwYxikvqAktslwMWoopJtMiig9KnQuhpGIGVAGaucYH2hGgswcYJQwKuN58iFEe3WC0EiNmYG1JRPWIHUukO4bVqwEZVNFSayaMfFLg3BZOvdQ3Eua60P2SYnGJcNAXYA_C571wazZN4AQdnuGHnLhud4NpwceQDrKF1tRe7uTDMxA",
"refresh_token": "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJraWQiOiJDQ0QwNEJCQTg0RkUxMUE3Nzc1RjAyQjgyNEQ5OEFGNjhEREQ4MEEwIiwidHlwIjoib2lfcmVmdCtqd3QifQ.KH_P3P_DfJmbHKKdqFh2rWaItAubi00dGOkYlyIUg2twzhwAepEvwrEwwk6WVzKv1XwXCBBRqz_Bgub0ks29TauUhYfpew1d6ZPS6C4WQCehcxLsA0LmD8HADvIuW-zhTOSIqP-JdZxaWCZ_jfi61FjjmO5y4iGKXADupid4kSW7CzuBDTscPMNQFs7u6RuCxEXAwtS35vqBR81jmZe-R7b8P87hT-jDef9H_syrzaiBDFBr_-QFN7xbIdOkmvLs4upeB07knj_JoB1V9vUOL9B3CNGT-0BKrU3HHxt2b05kx4YWIsdV7kHx3191HNZh_PkouyZrt9geLcT-dTlMXA.8g0Dk4t6w7rzEarhSPN3EA.yzAcqs3GblO9hlHBZ46-loYrkwE4iqH7qxCNjS0hTO9mGHyDPBuHmiVO2oC6w8btWJkTHU20DEtCiEA1162Lk8I3oezWbyCmXXYRW1Z-HE8waTPBUvm6hcyn6RF6ey9wzG-Qn2TOAOdq8jIxXMUueM1k4UVGhvPjeY6v4FhaNdJFv4l8XvwO9jmoBRGp0uL8n_07RYlY6tyb59CIVLiSvsA_-hDYlimVEUABa27AjQNKj-3ZwWr7JRAD7TPL0Ghbtr7eWNkQrf1zrZ99kIdtxpdAUaRMGCES-qiisbuW3g3b6RwRsE97JW5BBGqs552hZ4fLf1nVHU9U6J4MLrbKWxmCmX8DH3J0y9w0mj2Exa3wUHD7AId1FD2UEdR6y86zxfa7qrfvGXeAbIVtGAPXwcnnUT-M7HtqcHgO-AtJV1Xarpe8C7vDzlRbDGGWIpcUGbD6xWQPe-sTv1jqDcxZ3CX8naKkoojohGzZCUm6IIb_ADdsmuvcVFGuHdOcqc27QDukS_x9V4x1w918Lqi0rR1vBqgAuEPffowAUvDJJIWZPb9Q8JeyE2AF8cENp3HTGNhOweHiQkdtS836_WXA_R8A2bQU2R-hXWJpk56hlN9jZ--F6H1NcO3997SSeVHWQ1YUdzuju7j4HyGxOLx3yB0QpJplG8NGfZfSB2xq-Wi2qB6mW7edj4mL1PlBxGcrphxXeyd14WAMejsKQYueD-t1lt80NmRnkqvqtPARQHTjDcKfHLSbuFddQzlc5UCgQC97bIkM8WUgWTqH8rVNRe6gzQFVID4YxSZvs-aaCtJx0xDWaPTlyceRkaNBOuh4PGDtjRLSpcoh3EtdYL39PatbOM4-2FazY5z5r1rIYaXJ4NmAanH8GwV098TmH1sPQppsxrotKX9T5OqQxucdE2wAyrGCZM4ToyetP2SoJJCcisGU1mMy5Iv26TX9N1Wonu8URGBuhDGAd0GnR2ijX1xe6EEGaTWxUaKb5m8xfByIbXarb1TV2CB8XTwdCy3G_Y5QJQw5k88S7cnTZ4Lq5saPNIvFPhGPuxgrrCFcvP1SshMLHnge5p2mqiIKGTvK1ypZeqOdMVeQeo5HvFHoLTcntPO1n07BbL2K2q9Gq3Vvx5o-jq0cfB6lC63xImII2HrpS053Mng4daU-QIKUkaKsbEflYWVyOzBe6AgNcqHRZ3zxzZRaXn8mVuGz6YjgZE4xeS6M5e0XKlSAae-sPtr6c8sEvhBQ0tHTAWdsSk-OkuLp1f6likr4BvZwwkjgC-sYPqCiCg85npvcf28qZbh1Rs85g-g9FoRfIKljU2gyTAA1MPAx8f6EMhpBaQIi.quAj1ZNggBenfSMNmyKbvKuX_lTD_YFd6viyODV_Ugw"
}
Thank you all
Related
I am using Identity serve4 for user authentication and authorization and one of my clients is asp.net webform written on .NET 4.5. When the user tried to access the protected webform I am redirecting the user to identity server for authentication. But after authentication, there is a logic which based on currently logged userid, and for getting the current user login info I have to call some token endpoint which requires the token? I inspected the webform after login and I noticed there are some auth cookies. Now my question is how do I get token from this? On asp.net core we get an access token using the httpcontext below method but how do we get the same on asp.net webform using .NET 4.5?
var access_token = await HttpContext.GetTokenAsync("access_token")
The easiest way would be to save the access token in the cookie after authentication. Change the code on client to be like this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ResponseType = "id_token token",
Scope = "openid profile api1",
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = n =>
{
n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
return Task.FromResult(0);
},
}
});
And then you can retrieve the access_token from current user's claims, like this:
var accessToken = user.FindFirst("access_token").Value;
I explained it with details here: https://nahidfa.com/posts/identityserver4-and-asp-.net-mvc/#call-an-authorized-api-endpoint-using-access-token
Edit:
To set Identity.Name add this code:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
NameClaimType = "name"
}, // This is to set Identity.Name
});
I have the following scenario with net core 3. A web site with a login page. This login page sends the user and password to a Web API that response with a JWT token if the credentials are correct.
How can I set now my web user as authenticated? how can I set the claims of the web user with the claims I recieve from the API token?
Is it neccessary to add any service on the startup of something similar?
Could you provide me with any basic sample of how to do it or any documentation?
Thank you
You can use cookie authentication :
In the Startup.ConfigureServices method, create the Authentication Middleware services with the AddAuthentication and AddCookie methods:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
});
And enable middleware in Configure :
app.UseAuthentication();
app.UseAuthorization();
And in the action which user post credential to , you can send a http request to web api with credential , web api will validate the credential and return back jwt token , your web application then decode token and sign in user like :
var stream = "[token]";
var handler = new JwtSecurityTokenHandler();
var tokenS = handler.ReadToken(stream) as JwtSecurityToken;
var claimsIdentity = new ClaimsIdentity(
tokenS.Claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
RedirectUri = "/Home/Privacy",
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
Depending on your front end solution, you need to figure out how to decode the JWT that you received to retrieve the values that you need.
Here are a couple of things, again depending on what you are using on the front end
C#
https://developer.okta.com/blog/2019/06/26/decode-jwt-in-csharp-for-authorization
NPM Package for SPA
https://www.npmjs.com/package/jwt-decode
Here is another good resource for JWT
https://jwt.io/
You can take the JWT you received to view the values that are in it
I'm using ADFS 2019 and the scenario is:
Client App (trusted, client id and client secret)
Web Api (acts both as a server and as a client)
Resource to access
My GOAL is:
By using postman get a token from ADFS and call a Web API launched locally that must validate this token. Once the token has been validated it must generate another token (on-behalf-of) to access the last resource.
I can successfully get the first token specifying:
- Grant Type: Client Credentials
- Access Token URL: https://MY-ADFS/adfs/oauth2/token
- Client ID
- Client Secret
How can i configure my asp.net core Web Application to validate and accept this token?
I have all the data:
Web App identifier (for the server), web app client id/secret (when it acts as a client) and ADFS metadata endpoint.
I'm trying to do something like this:
services
.AddAuthentication(o =>
{
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://.../adfs";
options.Audience = "urn:microsoft:userinfo"; // taken from client token using jwt.io
options.MetadataAddress = "adfs metadata address";
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "https://.../adfs/services/trust",
ValidAudiences = new List<string> { "web app id" },
};
But it does not work (unauthorized or internal server error).
All these application are in the same application group in ADFS.
Thank you.
Update 1:
If i've understood correctly the audience must be WHO validates the token. So it must be the Web Api identifier inside ADFS.
If i put this identifier in the audience variable i get: audience did not match.
The audience that is in the token that i'm sending with postman is indeed different: urn:microsoft:userinfo!
Update 2:
I've managed to access to the web api and get a nice and valid access token. Now the problem is that the audience of the token is like:
"aud": "microsoft:identityserver:web api id on ADFS"
That "microsoft:identityserver is a problem when i have to do the "on-behalf of".
It forces me in doing:
ClientCredential clientCredential = new ClientCredential("microsoft:identityserver:client ID", "secret");
Otherwise it does not validate the audience.
But doing so, when i do:
var result = await authenticationContext.AcquireTokenAsync("resource to access' id", clientCredential, userAssertion);
It tells me that it cannot find a resource with client id "microsoft:identity:client id", and that's true, because the resource on ADFS has a client ID WITHOUT the "microsoft:identity" part.
I found a tutorial where I can sign in to my application with Azure AD credentials.
In my frontend I'm using Xamarin.Forms.
In my backend I'm using ASP.NET Core 2.0 WebApi.
Backend:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]);
options.Audience = Configuration["AzureAd:Audience"];
});
}
It's pretty simple.
In my frontend I'm filling in my credentials and asking for a access_token.
{
"token_type": "Bearer",
"scope": "user_impersonation",
"expires_in": "3600",
"ext_expires_in": "0",
"expires_on": "1507104075",
"not_before": "1507100175",
"resource": "my_resource",
"access_token": "my_access_token",
"refresh_token": "my_refresh_token"
}
The access_token i'm filling in the headers with Authorization set with bearer my_access_token.
My Api know's all my information because it will automaticly set claims with the info from my access_token. This info is provided by Azure AD. (fullname, firstname, lastname, ...)
But how can I get this information in my frontend?
You might want to check out the active-directory-dotnet-native-desktop sample on GitHub.
I shows how to use ADAL.NET in a desktop app, to get a token for a service. you will need to adapt it for your Xamarin forms client, but the principle is the same as far as authentication is concerned.
Also it contains a service and you would replace it by your own service and get a token for your web API by changing the resource ID to be the one of your application created using the ASP.NET wizard (you'll find it in the Azure portal as described in the readme.md of the sample)
the idea is that you first get a token using ADAL.Net line 92 of TodoListClient/MainWindow.xaml.cs
result = await authContext.AcquireTokenAsync(todoListResourceId, clientId, redirectUri, ...)
and then you use it as a bearer token line 121
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
If all the info you required is include in the access token, you can just decode the access token on the client. The access token is a JWT, it is easy to research code sample to decode the access token like following threads:
How to decode JWT Token?
Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt
And if you also require more user info, you can refresh the access token for the Microsoft Graph, and call the me endpoint of Microsoft Graph(refer here). And below is the document about how to refresh the access token via refresh token:
Refreshing the access tokens
I have a ASP.Net Core MVC Web App that users needs to logon to get the id_token from the IdentityServer4 and then that id_token will be passed to webapi implemented in ServiceStack to obtain the authorization code. The subsequent call to the webapi will use the authorization code.
So far what I have read is for the Web App, it should use openid cookie token (UseOpenIdConnectAuthentication). For the webapi, it should use the bearer token. My question is how I can pass that http only cookie token from the client side browser as a bearer token in the http header. As the cookie is http only, it can't be accessed by the Javascript. Moreover, the ASP.NET Core cookie middleware encrypts the cookie, can that encrypted cookie be decrypted by the ServiceStack webapi (if the cookie is passed to the webapi)?
Am I going in the right direction? Any suggestion is welcome.
Thanks
You can find an example of your scenario here: https://identityserver4.readthedocs.io/en/release/quickstarts/5_hybrid_and_api_access.html
The authorization code is only used to get access tokens from the identity server, it is not used to authenticate to APIs.
Here is how the flow should work:
User logs in at Identity Server
Your MVC app gets an authorization code and id token
The id token tells your MVC app who the user is
The authorization code is exchanged for an access token and refresh token with identity server for the API
Now the MVC app can make HTTP calls from its backend using the access token
Authentication cookie is created and returned to user
Front-end submits the authentication cookie with every request to MVC backend, which authenticates every request automatically that hits MVC, then when you want to call the API from there, get it as shown in the docs, and attach it to your requests
I think the point you are missing here is that once the user is logged in, you will get the access token in the response as well when you land back on the client application. If you are using Hybrid Flow, on the client app we configure it as
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
See the ResponseType we ask for code i.e the access code. So you need not to call or login again. Once you want to call your api just get the token like
var access_token = await HttpContext.Authentication.GetTokenAsync("access_token");
// call api
var client = new HttpClient();
client.SetBearerToken(access_token);
var response = await client.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
And if you using Implicit flow, your front end can get the access token using oidc-client library and user.access_token will have it.