How to exchange an authorization token on the server side - asp.net-core

I've got an ASP.Net Core 2.1 Web API, using OAuth authentication. In addition, we are using claims-based authentication, with the claims encoded in the auth token.
This has been working well for a while, but lately things have gotten out of hand when a new requirement came up that radically multiplied the number of claims that had to be stored in the token. To cut a long story short, we're now at the point where the auth token is so long that we had to reconfigure our server to allow headers over 128k.
This is obviously untenable; just from the perspective of network traffic it's absurd to have to send over 100k of headers along with the simplest GET request. So now I'm thinking along the following lines: instead of encoding the claims in the token, just use a Guid token and store the claims in the database along with the token, so when we validate the token we can pull out the claims at the same time and exchange those locally.
Theoretically this should solve our entire problem in one fell swoop, but I'm just stuck on the implementation details, specifically: since the claim-based authentication is expecting to find the claims encoded in the auth token, how do I swap out the Guid auth token for the one that includes all the claims?
Existing code:
public class MyAuthenticationEvents : OAuthValidationEvents
{
public override async Task ValidateToken(ValidateTokenContext context)
{
if (context.Properties.ExpiresUtc < DateTime.UtcNow)
{
context.Fail("Access Token has expired.");
return;
}
if (!await TokenIsValidAsync(context)) // code to validate the auth token against the database, could be modified to return claims
{
context.Fail("Access Token has not been properly set or has been invalidated.");
return;
}
// Here I would expect to do some skullduggery to switch the compact Guid token for a large token containing claims
context.Success();
}
}
What's the correct syntax? Or am I barking up the wrong tree, and there's an entirely better way of achieving my objective?

Turned out to be pretty simple. All I needed to do in the ValidateToken() method was to create a new ClaimsPrincipal using a ClaimsIdentity which I reconstructed based on the serialized claims that I stored on the database:
... // create claimsIdentity from serialized claims
context.Principal = new ClaimsPrincipal(claimsIdentity);
context.Success();
}
Just one thing to take note of: on the initial login, it is mandatory to return a Subject claim, so in the HandleTokenRequest() method we still need:
identity.AddClaim(new Claim(OpenIdConnectConstants.Claims.Subject, user.Username));

Your existing authentication middleware is populating claims on a ClaimsPrincipal based on the contents of the JWT, but by the time your authorization handlers inspect the claims they're not read directly from the token, so there's no need to modify the token itself.
You should be able to access and modify the current user (HttpContext.User) claims from middleware, so you can make custom middleware that executes after the authentication middleware and adds claims based on the results of the database lookup.
Edit: As Shaul points out in his answer, this can also be done in the Validate method of your authentication components. That's actually a good place to do it in this case. Separate middleware (as I described) would work if you wanted the 'translation' of claims to those from a database to be decoupled from the auth process. The key point is that ASP.NET Core stores user claims in ClaimsPrincipal objects and you just need to update that rather than worrying about the token itself.

Related

API Authentication using multiple Authentication Providers

This topic is one that feels like it should be documented better - or perhaps I am using the wrong terms when searching.
I have several SPA apps that use various Oauth2 logins
(ie. Okta, Facebook, Google) to authenticate and generate access
tokens.
These apps all access a common API backend (asp.net core). All
requests to the API have the Oauth2 access token attached as an Authorization header.
How do I configure this single backend API to validate these access tokens from one of a variety of providers, without knowing in advance which access token is attached, and decode a user email address that I can use for further authorization purposes?
I have found much documentation on validating tokens from a descrete, known authorization provider, but very little on using multiple providers. With all the apps out there that give you a choice of Oauth2 logons to choose from (StackOverflow among them), I thought this would be a more common problem.
What am I missing!?
It seems like the correct way to address this situation is to build a Custom Authentication Handler as documented here: https://referbruv.com/blog/posts/implementing-custom-authentication-scheme-and-handler-in-aspnet-core-3x
In this Authentication Handler I can decode the token, assert that the issuer is a member of a whitelist, validate the access token using the issuer's public key, and use the rest of the token to build the Identity I need for further authorization.
At least now I have a better idea what to search for, and I'm not completely re-inventing the authentication mechanism!
You will want to identify the user in a consistent way in your APIs, then authorize requests based on the identity + scopes.
This will be very difficult when using many different token providers, as you are finding. Their access tokens are not designed for you to use in your own APIs.
A better mechanism is to use tokens only from your own Authorization Server, to support different login methods but also put your code in control. My Federated Logins blog post has further info.
It turns out I was overthinking this after all.
Since I am dealing with an API backend, all I needed to do was to validate IDP Bearer tokens, not to create them. In the end, I was able to validate 3 ID providers using the folowing simple code:
services.AddAuthentication(OKTA_SCHEME)
.AddJwtBearer(ADFS_SCHEME, options =>
{
options.Authority = adfsConfig.authority;
options.Authority = adfsConfig.authority;
})
.AddJwtBearer(GOOGLE_SCHEME, jwt => jwt.UseGoogle(
clientId: googleConfig.clientId
))
.AddJwtBearer(OKTA_SCHEME, options =>
{
options.Authority = oktaConfig.authority;
options.Audience = oktaConfig.audience;
});
Note that this required the installation of one additional nuget package to simplify the validation of the Google tokens, which don't appear to follow the standard: Hellang.Authentication.JwtBearer.Google.
At this point I can authorize using attributes like:
[Authorize(AuthorizationSchemes = OKTA_SCHEME)]
...or set up policies based on the schemes.
The second part problem was to link my various logons to users in a local database, which I ended up doing using a custom IClaimsTransformation that uses the information populated to ClaimsPrincipal to lookup a the user in my database, and add an "Employee" role claim, if they are found.
public class EmployeeClaims : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(a => a.Type == "EmployeeNumber"))
{
Employee employee = lookupEmployee(principal);
if (employee != null)
{
ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim(ClaimTypes.Role, "Employee"));
id.AddClaim(new Claim("EmployeeNumber", employee.EmployeeNumber.ToString()));
principal.AddIdentity(id);
}
}
return Task.FromResult(principal);
}
private Employee lookupEmployee(ClaimsPrincipal principal) {
string issuer = principal.Claims.Single(a => a.Type == "iss").Value;
if (issuer.Contains("google.com"))
...
}
}
This IClaimsTransformation is then registered by:
services.AddScoped<IClaimsTransformation, EmployeeClaims>();
Now I can additionally authorize employees with:
[Authorize(Roles = "Employee")]

Verifying a JWT that DNN (DotNetNuke) created in a microservice

I'm playing around with DNN 9 and it's implementation of JWT at the moment.
Their implementation is here - https://github.com/dnnsoftware/Dnn.Platform/tree/development/DNN%20Platform/Dnn.AuthServices.Jwt
I'm wanting to write microservices in Docker containers that all look back to DNN's SQL. However, I need to understand how to verify the JWT that will be sent via the client to make sure it hasn't been tampered. DNN's web api implementations do this automatically. The problem is I don't see how I can verify that the token is valid because there is no 'secret' that i have to add.
I would want to get the JWT from the DNN enpoint and use this in a microservice which isn't running with DNN but will be able to read the data from the DNN database.
Could anyone provide any input?
Thanks :)
If the services have access to the DNN database then you can use the validation as implemented by the referenced code.
It seems that the tokens are not real JWT tokens, in the sense that they are self-contained, though they are not real reference tokens either. The tokens are persisted in the database, but contain public data as well.
How this is implemented: the token is identified by a sessionId and the secret is a concatenated string, converted to byte array:
var secret = ObtainSecret(sessionId, portalSettings.GUID,
userInfo.Membership.LastPasswordChangeDate);
The data is a JWT. It contains a Header, a Payload and a Signature.
From the received token first the Header (and scheme) is validated. Then the RAW token is read, expiration is verfied and the existence of the SessionId is checked, which is added as claim (TokenId).
The final step is to lookup the user from the store (cache / database), using the SessionId:
private UserInfo TryGetUser(JwtSecurityToken jwt, bool checkExpiry)
{
// validate against DB saved data
var sessionId = GetJwtSessionValue(jwt);
var ptoken = DataProvider.GetTokenById(sessionId);
When the SessionId is not found, the token is not valid. Again expiration is validated.
And finally the received data is compared to the persisted token, line 423:
if (ptoken.TokenHash != GetHashedStr(jwt.RawData))
This should prevent the token from being tampered.
This means for you that you can use the same code for your microservices, provided that the services have access to the DNN database. Call ValidateToken(), which will return the username when valid or null when invalid.
An alternative is to publish your own JWT tokens like I described here. You can create a central API or use a fixed, shared secret. E.g. a certificate that is available to the microservices. In that case you are dealing with self-contained JWT tokens which can be validated using the default validators. And you don't need access to the database.

Should Name Claim remain omitted in JWT Access Token with IdentityServer4 (using Identity) and ASP.Net Core API?

While configuring my IdentityServer4 (using Identity) resource owner grant flow with an asp.net core API backend, I got to thinking that perhaps the "Name" claim should remain omitted in the JWT access token for user security? This claim is not available with out of the box behavior of IS4.
Previously, I had been adding in the "Name" claim for the access token in my IS4 Config.cs file as follows:
var claims = new List<string>
{
JwtClaimTypes.Name
};
return new List<ApiResource>
{
new ApiResource("api1", "Auth API", claims)
};
I was doing this because it allows a straightforward approach to get a logged in user's ClaimsPrincipal.Identity.Name for user look up inside a Controller action.
var name = User.Identity.Name;
var user = await _userManager.FindByNameAsync(name);
However, IS4 access tokens (when using Identity) include the user's GUID id in the "Sub" claim. With this, we can also look up a user using the following:
var userId = User.Claims.FirstOrDefault(u => u.Type == "sub").Value;
var user = await _userManager.FindByIdAsync(userId);
I know there is slightly more processing with the LINQ query (hardly anything tbh), but I was thinking it might be of worth to protect a user's username (email address in my situation) if an access token ever fell into the wrong hands. Especially since JWT's are so easy to decode with the likes of jwt.io.
Do you guys agree or disagree? Or am I looking at this the wrong way and missing something?
JWT usually contain the public data and it is self-contained. i.e. You don't need to communicate with a backend server to construct user's identity. You should prevent the token fell into wrong hand by using https. Also, you should balance your token validity window(usability vs security) and use a nonce for maximizing the security.
I don't think 'name' should be omitted from claim collection. A valid use-case for what you are doing is that you need to make sure that changes to your user store immediately reflect in your web API. In the case of a self-contained token, if you change the 'name' in the data store, the user will not see that change until he was issued a new token. In this case use of a 'reference token' might be a good option.
Also, It looks like you are directly accessing user store from the web API. While you might have valid reasoning behind this, Idea of using token based authentication is to delegate authentication to external party(Identity Server). So common pattern is to
Include every public data that you require in the web API in the
access token.
If token getting too big, include a subset of claims in the token and query user info endpoint when required.
Use reference tokens if you have valid reasons to do so. But this will affect the performance as it will require back channel communication with identity server.

Get JSON Web Token payload data within controller action

I'm implementing JWT based authorization for my ASP.NET Web API application with Angular2 SPA. All is well and clear regarding authorization flow except for one detail. I am wondering how to get JWT payload information within the Web API controller action?
Looking through the web I can't find any solution that I would go for, for example, setting Thread.Principal or something like that.
What are the recommended ways to accomplish that?
The normal process to handle a JWT token as authentication in ASP.NET is:
Get the token from the request and ensure is valid.
Create a principal based on the information contained within the token and associate it with the request.
This implies that the payload information within the token is available through the request principal usually in the form of claims. For example, if your JWT contains information about the user roles it would get mapped to a role claim available in the principal.
You don't mention if you're using OWIN or not so I'll assume OWIN for the example, but it shouldn't really matter or be much different at the controller level.
Despite the fact you're concerned only with the data consumption part, if curious, you can read through this set of tutorials about ASP.NET Web API (OWIN) for a more in-depth look on the whole process:
Introduction
Authentication (HS256)
Authorization
The last one would be the one with most interest , you'll note the following code snippet on that one:
[HttpGet]
[Authorize]
[Route("claims")]
public object Claims()
{
var claimsIdentity = User.Identity as ClaimsIdentity;
return claimsIdentity.Claims.Select(c =>
new
{
Type = c.Type,
Value = c.Value
});
}
This relies on the User.Identity available within the controller to list all the claims of the currently authenticated identity. Unless you have an authentication pipeline configured rather different then what it's the norm, these claims are mapped from the JWT payload your API receives.

OpenID Connect server. Generate access token based on 3-rd party token (social login)

I had implemented OpenID Connect server that generates access tokens for mobile client based on username/password using OpenIddict.
My next goal was to provide ability to generate Access Token using 3-rd party tokens (social login for example), and I started from integration with Google token, but stuck as cannot find any samples/informations about how to do this.
The only one idea that I currently have is to make request to "/connect/token" endpoint and send Google token in "code" parameter, for example in "google:" format, then override OpenIdConnectServerProvider.DeserializeAuthorizationCode method:
Called when receiving an authorization code. An application may use this context to deserialize the code using a custom format and to skip the default logic using
So I have created own CustomProvider class based on OpenIddictProvider, registered it
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext, int>()
.Configure(builder =>
{ builder.Provider = new CustomProvider(sp.GetRequiredService<SignInService>()); }
and overridden the DeserializeAuthorizationCode method:
public override async Task DeserializeAuthorizationCode(DeserializeAuthorizationCodeContext context)
{
string code = context.Request.Code;
if (code.StartsWith("google:"))
{
string token = code.Replace("google:", "");
var principal = new GoogleTokenValidator().ValidateToken(token, null).Result;
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), "Bearer");
ticket.SetPresenters(context.Request.ClientId);
context.Ticket = ticket;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(1);
context.HandleResponse();
await _signInService.Login(principal);
return;
}
else
{
base.DeserializeAuthorizationCode(context);
}
}
where GoogleTokenValidator is a custom class for Google token handling (it makes call to Google User Information Endpoint and generate ClaimsPrincipal), based on "copy-pasted" code from GoogleHandler class in aspnet/Security repo.
In general it is working with some additional hacks, but I have strong feeling that reinventing the wheel...
In general it is working with some additional hacks, but I have strong feeling that reinventing the wheel...
You're not only reinventing the wheel, but you're also implementing something totally non-standard that is not supported (at all) by OpenIddict.
Here's the approach I recommend (which is the one we use in the MVC server sample):
The OAuth2/OpenID Connect client application redirects the user agent to your authorization controller (you can take a look at this controller for an example).
OpenIddict will validate the authorization request and allow your controller to be invoked if it's fully valid.
If the user is not already logged in, your authorization controller will redirect the user to the login endpoint, provided by AccountController. At this point, you're free to propose local authentication (e.g using a username/password couple) or Google authentication (you can use the Google authentication middleware for that). You can even offer 2-FA as part of this login process.
Once the user is logged in (e.g after a registration process and/or an external authentication association), his/her browser is redirected back to the authorization endpoint, where a consent form indicating he/she's about to allow your JS app to access his personal data on his/her behalf is displayed.
When the user allows your client application to access his data, the request is handled by your authorization controller, that calls SignInAsync to inform OpenIddict that an authorization code/access token should be returned to your application.