I have an MVC application in which I use OpenIdConnectAuthenticationMiddleware to authenticate the user against AAD. This MVC application uses a few backend services that require the user's authentication context.
If I register these services separately in AAD, I can get a token for them using
AuthenticationContext.AcquireTokenSilentAsync. But registering these services separately with AAD seems wrong as they would require the user to consent to them separately (they are really part of the application).
So I'd like to use the JWT token I got from AAD when the user authenticated and use that as the bearer token for calling the downstream services. I realize that these services need to have the same audience as the MVC application.
But how do I get that JWT token. The ClaimPrincipal's first identity does not have a bootstrap context.
Please note that having your services admit tokens with the same audience opens you up to token forwarding attacks. I would not recommend that. Also, the consent should happen in a single page and with a single click - hence in terms of user impact there isn't really much difference.
That said. If you are really set in it, you can enforce the presence of the token in the bootstrapcontext by switching to true the flag SaveSignInToken. See
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters{SaveSigninToken=true},
PostLogoutRedirectUri = postLogoutRedirectUri
});
Edit The below is one way to achieve this, but it has some security implications. There is also a flag you can set for using the bootstrap context. Please see vibronet's answer for more.
In the OpenIdConnectAuthenticationOptions, if you configure a handler for the SecurityTokenValidated or AuthorizationCodeRecieved notifications, you can access the id_token in the notification's properties. You can then use that id_token as the bearer token in your service calls. There are several different ways you might make that id_token available in your controllers.
One caveat: the id_token will have the clientId of your web app as the aud claim, not the app id uri. So in your services, you should use the clientId guid as your audience.
Related
So, I'm trying to implement an OIDC client application using ASP.NET Core 3.1. I am trying to leverage the .AddOpenIdConnect() and .AddJswtBearer() middleware. However, I need some clarification on what this middleware is doing.
Here is what I currently have for the middleware configuration:
.AddOpenIdConnect(options =>
{
options.Authority = Configuration["auth:oidc:authority"];
options.ClientId = Configuration["auth:oidc:clientid"];
options.ClientSecret = Configuration["auth:oidc:clientsecret"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["auth:oidc:authority"];
options.Audience = Configuration["auth:oidc:clientid"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = Configuration["auth:oidc:authority"],
ValidAudience = Configuration["auth:oidc:clientid"],
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
}
I notice that requests to the Authorization server's /.well-known/oidc-configuration and /.well-known/keys endpoints are requested when the application is first started, based on my Fiddler capture below
Where does it do that?
I'm trying to also validate that the JWT received from the authorization server is valid (that it hasn't been tampered with between the time the server sent it, and the time that the client has received it). I understood this to happen when I added the TokenValidationParameters object in the .AddJwtBearer() middleware. To test this, I tried changing Valid Audience in the TokenValidationParameters to something like asdkwewrj which I know is not the valid audience for my token. But, I never got an error from the client saying that the audience was invalid. The authentication still worked, and I was able to access my secure dashboard still.
Another thing I'm trying to implement is refresh_token grant_type with this OIDC client. I thought that the options.saveTokens in the .AddOpenIdConnect() middleware would allow me to save the tokens. It looks like they're save as cookies, but these cookies look nothing like my token values (my access token is a JWT, but out of the cookies I see, none of them begin with ey).
In a nutshell, I'm trying to understand the following:
Does this .AddJwtBearer() middleware validate the ID Token for me if I have the correct JwtBearerOptions defined (like I do above)? Or do I need to manually validate the ID token against the JWKs from the JWKs URI?
If I have to manually validate the ID token using the JWKs from the JWKs URI, how do I store these JWKs when the middleware makes the request to the /.well-known/keys endpoint?
How do I get the cookies that correspond to the access token and refresh token, and then send the refresh token to my authorization server?
I noticed that I can utilize options.Events in both of these middlewares. Would any of those solve any of the items I'm trying to accomplish?
Overall, what do these two middlewares handle for me, that I shouldn't need to manually do (i.e token validation and/or token renewal)?
Thank you! I am still fairly new to in-depth ASP.NET development like this, so I appreciate any responses.
First of all, the OIDC authentication scheme and the JWT bearer authentication scheme are independent of each other. OIDC is mostly used for server-side authentication and will pretty much never be used on its own but always with the cookie scheme. The reason for this is that the OIDC scheme will just be used for the authentication but is not able to persist the information on its own. I’ve went into more details in a different answer of mine that also explains how the authentication flow works with OIDC.
As for JWT bearer, this authentication scheme will run on every request since it is completely stateless and expects clients to authenticate themselves using the Authorization header all the time. This makes it primarily used for protecting APIs since browsers wouldn’t be able to provide a JWT for normal browser requests.
So you should first ask yourself whether you are protecting your server-side web application (e.g. using Razor views or Razor pages) in which case you want to use OIDC and the cookies authentication scheme, or if you are protecting your API. Of course, the answer could be “both” in which case you want all of those three schemes but ASP.NET Core will not support this without further configuration.
With that being clarified, let’s get into your questions:
The requests to /.well-known/oidc-configuration and /.well-known/keys are done by both the OIDC and the JWT bearer scheme in order to retrieve information from your identity provider. They will do that regularly to update their data, including information about the signining keys which they will use to validate the tokens. This happens within the scheme handler and is usually not visible to you.
Correctly set up, the JWT bearer authentication will validate the token for you. It will do that by verifying the signatures using the retrieved signing keys, and then it may check additional properties like the specified audience or its lifetime.
You shouldn’t ever need to validate tokens manually. That’s the job of the authentication scheme. You are using the authentication stack so you can just access the user principal within your app without doing anything.
Cookies are protected using data protection so that they are safe against forgery. In order to retrieve the tokens stored with SaveTokens = true, you can use the GetTokenAsync method on your HTTP context.
You can use the authentication events to add to the default behavior of the authentication schemes. For validating your tokens using the standard mechanisms, you shouldn’t need to though.
There is just one middleware: The authentication middleware. It uses the configured authentication schemes to perform the authentication of users so that—once set up correctly—the authenticated user is available throughout the application, e.g. in controllers, MVC filters, Razor views, etc.
Does this .AddJwtBearer() middleware validate the ID Token for me if I
have the correct JwtBearerOptions defined (like I do above)? Or do I
need to manually validate the ID token against the JWKs from the JWKs
URI?
AddJwtBearer is only used by APIs to validate the access token and create a user (ClaimsPrincipal) out of it. It's all it does. It does not deal with id token.
In general its easier to put the API on a separate service, to make it more clear who is doing what. when you mix both the client and API in the same service, it can be harder to reason about it.
If I have to manually validate the ID token using the JWKs from the
JWKs URI, how do I store these JWKs when the middleware makes the
request to the /.well-known/keys endpoint? How do I get the cookies
that correspond to the access token and refresh token, and then send
the refresh token to my authorization server?
The ID token is validated and handled for you by AddOpenIdConnect. You dont need to validate the ID-token by yourself. AddOpenIdConnect will create the cookie and optionally store the tokens as well in the cookie.
Overall, what do these two middlewares handle for me, that I shouldn't
need to manually do (i.e token validation and/or token renewal)?
To summarize:
Ues .AddOpenIdConnect() for the client, that allows the user to login.
Use .AddJswtBearer() for the backend APIs.
Token renewal is a different story that none of them handles out of the box. For that, you can consider to use IdentityModel.AspNetCore or do something by yourself.
We have a system that uses C# Core 2.1, IdentityServer4, and Identity to authenticate users. Various other projects use the system for authorization. I can create policies in my API's that check user claims; and use those policies to secure resources. I add code similar to this in the API Sartup.cs:
services.AddAuthorization(options =>
{
options.AddPolicy("example",
policy => policy.RequireClaim("claim", "data"));
});
And add the following code before my API controller or specific task:
[Authorize(policy: "example")]
We have used this system for a long time. Now we want to lock down an action so that only a specific client can do it (not their users). But claims obtained through the grant type client_credentials are either not being added to the access token, or not being seen by the Authorization service.
Is there a way I can see what claims are in a token when it does not have openid as a scope?
Assuming the claim is there, why isn't the Authorization service able to see it?
Is there another alternative? We want to lock down an action so that only the client apps themselves can do it.
First you can always capture the raw tokens using Fiddler to see what claims that are actually passed to the receiver of the tokens. Then check in the User (ClaimsPrincipal) created by the authentication handler what claims it contains.
Then you need to explicitly map/add the missing claims so that the expected claims get into the claimsPrincipal User object. Some claims are removed in that process by default.
I'm using IdentityServer4 with a mix of v4/v3 clients.
I have custom profile data that is store on the application side that I'd like to include in the access_token so that my downstream APIs can use this with bearer/jwt authenication.
I understand I can manipulate claims via IProfileService, but that is registered on the identity side, not the application.
How can I get my custom profile claims into the requested access token?
Additional Details
I've done a proof of concept using Extension Grants to specifically pass my application claims through the IdS so that it includes those in the token. It works...but feels pretty hacky.
Please do not do that. The JWT token is sent with every request.
if the downstream API needs something from the user, then either submit it with the call, or have an endpoing the downstream api can call. Embedding rarely used large inforamtion in someting transmitted every call (except in http 2.0) is a nonononono.
You can not change jwt token content after being created and signed by authorization server. But you can use ClaimsTransformation to manipulate claims on the api project.
Edit: Another option to use JwtBearer OnTokenValidated event.
Any claims issued from your implementation of IProfileService should end up in the token.
Note that your implementation of IProfileService should check if it is issuing claims related to IdentityResources or ApiResources. It would be a bit pointless adding api claims to an id_token.
When the client receives the token from you IDS, it will pass it in calls to your API.
If your client is using cookie authentication, the tokens themselves as well as some user profile claims will be stored in the authentication cookie. This obviously depends on the flow your are using Implicit, Hybrid etc.
If you want to inspect what you get back from the IDS at the client you could add a Cookie Authentication Event handler (eg OnValidatePrincipal) to see whats stored in the cookie, or add an OnUserInformationReceived event handler to your OIDC handler and inspect what you get back in there.
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.
I've been following the Thinktecture Identity Server example of OAuth2 Resource Owner Password Flow found at http://leastprivilege.com/2012/11/01/oauth2-in-thinktecture-identityserver-v2-resource-owner-password-flow/
I have the example working and returning JWT tokens successfully via the following process
Use the Thinktecture OAuth2Client to retrieve the access token
Retrieve the signing certificate from the "Trusted People" store on the client machine
Using the certificate and creating a new JwtSecurityTokenHandler and TokenValidationParameters and calling tokenHandler.ValidateToken to get a ClaimsPrincipal
From here I am authorized, but I am uncertain of the best way to persist the token for further requests. I tried using
var sessionToken = new SessionSecurityToken(principal, TimeSpan.FromHour(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
But I do not have a SessionAuthenticationModule registered. I tried using the Identity and Access wizard to get this in place, but it makes many changes to config and tries to set things up for passive authentication.
I could use a traditional FormsAuthentication cookie (.aspnetAuth) but I remember discussion that an advantage of the .FedAuth cookie was that it was naturally split into several cookies if the size grew too big.
I'm struggling to find an article that completes the picture for me. I need the bearer token for accessing various APIs further down the stack. I have working examples of this for SSO/passive authentication, because most of the work is done for you. I'm just not sure of the best pattern for use when using the Resource Owner Password flow.
So
Have I missed a more straightforward way to achieve this with Thinktecture Identity Model and Server?
Should I try to create a FedAuth cookie so that I can reuse the various Messagehandler/filter components that are already setup for WIF?
Otherwise - is there anything particularly wrong with simply putting the access token in the UserData section of the FormsAuthentication cookie?
Try to look at this question: WIF Security Token Caching.
I believe this code might do
var sessionSecurityToken = new SessionSecurityToken(principal, TimeSpan.FromHours(Convert.ToInt32(System.Web.Configuration.WebConfigurationManager.AppSettings["SessionSecurityTokenLifeTime"])))
{
IsPersistent = true, // Make persistent
IsReferenceMode = true // Cache on server
};
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionSecurityToken);