Get current authentication scheme in ASP.NET core 2 - authentication

I am currently struggling with an Asp.net core 2 application which uses two openid providers for authentication, mapped to two different Authentication Schemes (with different names).
The problem I am facing is trying to logout of the specific scheme that is currently being used. For example, if I support both Google and Facebook authentication, I need to understand which scheme is currently being used, and call the SignOut method indicating the correct scheme. This allows me to clear the local cookies and also redirect the user to the external identity provider and logout.
The thing is that I am not able to find a GetCurrentScheme() sort of function so that I can use to then specify the scheme in the SignOut method. I am sure I am missing something basic...

There does not seem be any direct way to find current scheme. Here is a crude way:
private readonly IAuthenticationSchemeProvider authenticationSchemeProvider;
...
foreach(var scheme in authenticationSchemeProvider.GetRequestHandlerSchemesAsync()){
var authResult = await context.AuthenticateAsync(scheme.Name);
if(authResult.Succeeded)
// this is the scheme that was used for authentication
}

I had the same question, but I finally put the authentication scheme in the claims collection in my SignIn method :
claims.Add(new Claim(ClaimTypes.AuthenticationMethod, authenticationScheme));
So, in the SignOut method, I can retrieve the authentication scheme :
var authenticationScheme = HttpContext.User.FindFirstValue(ClaimTypes.AuthenticationMethod);

IAuthenticationHandler has also Task InitializeAsync(AuthenticationScheme scheme, HttpContext context). It is called before AuthenticateAsync scheme name is here in first parameter.

Related

Can I Combine Identities from two Authentication Schemes in ASP.NET Core 3?

I have a web application that utilizes our organization's CAS servers for authentication. I am currently developing an integration with DocuSign for this application. A user will first come to our site and sign in with CAS. Then, they can go to the DocuSign area of the application, sign in to DocuSign, and then perform some functions tied to the DocuSign API. Each of these pieces works properly within my application.
My current problem is that I cannot access both Identities within the HttpContext simultaneously. I need the CAS Identity to determine user behavior and access to certain functions. I need the DocuSign Identity to get the necessary values to enable the API calls. In Startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/Login");
})
.AddCAS(options =>
{
options.CasServerUrlBase = Configuration["CasBaseUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOAuth("DocuSign", options ->
{
//necessary DocuSign options
});
I have a HomeController that uses the Authorize attribute and properly requires CAS to access. I can properly access the claims from the CAS Identity. I have a DocusignController where I use Authorize(AuthenticationSchemes = "DocuSign"). An ActionFilter that applies to this controller shows that the DocuSign Identity is coming through properly. Unfortunately, the CAS Identity is not attached, so I cannot check for things such as admin permissions. I examined the cookies, and saw that the .AspNetCore.Cookies value changes when I go between the different Authentication Schemes.
One attempt at a solution was to change the DocusignController to have Authorize(AuthenticationSchemes = "DocuSign, CAS" as its attribute. This seems to be an 'or' rather than an 'and' of the two schemes. If CAS was the most recent scheme, then the CAS Identity is the one seen in my ActionFilter. Same for DocuSign.
Another attempt was to create my own AuthenticationHandler to combine the two Identities. Appended to the AddAuthentication above:
.AddScheme<CombinedSchemeOptions, CombinedHandler>
("Combined", op => { });
Then, within this CombinedHandler:
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var CasResult = await Context.AuthenticateAsync("CAS");
if(CasResult?.Principal != null)
{
principal.AddIdentities(CasResult.Principal.Identities);
}
var DSResult = await Context.AuthenticateAsync("DocuSign");
if(DSResult?.Principal != null)
{
principal.AddIdentities(DSResult.Principal.Identities);
}
var ticket = new AuthenticationTicket(principal, "Combined");
return AuthenticateResult.Success(ticket);
}
This seems to behave the same way as before. The most recent Scheme I've used, upon entering the Combined scheme, will give a successful Result with an Identity, and the other Result will return with a failed result.
Is it possible for me to have these two Identities within the HttpContext.User simultaneously? Is it otherwise possible for me to combine claims from the two Identities into one and pass that along?
Further research and tinkering has led me to a solution. First, I discovered that I needed the two authentication schemes to write to separate cookies. I then made sure the DocuSign scheme used the new cookie as its SignInScheme.
.AddCookie("DSCookie")
.AddOAuth("DocuSign", options =>
{
options.SignInScheme = "DSCookie";
//further DocuSign options
}
I also learned that the Authorize tag works as an OR statement (if you have CAS OR DocuSign, you are authorized for this action), yet it will pass along all valid identities permitted by the scheme (You have CAS and you have DocuSign? Bring both Identities in with you). Thus, the second part of my solution has a single action that requires DocuSign authentication, prompting the necessary challenge. Then, I move to actions that have both authentication schemes, and both Identities are brought along properly.
EDIT: To elaborate per Dmitriy's request
[Authorize(AuthenticationSchemes = "DocuSign")]
public ActionResult Index()
{
return RedirectToAction("Control");
}
[Authorize(AuthenticationSchemes = "DocuSign,CAS")]
public ActionResult Control()
{
//relevant code
}
I click on a link to go to the Index action. Since I currently do not have a valid DocuSign Identity, it performs the necessary prompt and takes me to the sign in screen. If I linked directly to Control, Authentication would see that I currently had a valid CAS Identity, so would approve my access, even though I did not yet have the DocuSign Identity.
After the sign in, when I arrive at the Index function, only the DocuSign Identity is currently available. I then redirect to Control. That function verifies I have at least one of the specified Identities, grants me access, and passes along any of the listed Identities I currently possess. Thus, inside Control, I can grab information from both CAS and DocuSign.

How to exchange an authorization token on the server side

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.

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.