简体   繁体   中英

ASP.Net Core MVC/API/SignalR - Change authentication schemes (Cookie & JWT)

I've a .Net Core 2.2 web application MVC in which I've added API controllers and SignalR hubs. On the other side, I've a mobile app that calls the hub methods. Before calling hubs from the app, I am authenticating my users through an API call - getting back a JWT Token - and using this token for future requests, this way I can use Context.User.Identity.Name in my hub methods:

public static async Task<string> GetValidToken(string userName, string password)
{
   using (var client = new HttpClient())
   {
     client.BaseAddress = new Uri(_API_BASE_URI);
     client.DefaultRequestHeaders.Accept.Clear();
     client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

     LoginViewModel loginVM = new LoginViewModel() { Email = userName, Password = password, RememberMe = false };
     var formContent = Newtonsoft.Json.JsonConvert.SerializeObject(loginVM);
     var content = new StringContent(formContent, Encoding.UTF8, "application/json");
     HttpResponseMessage responseMessage;
     try
     {
        responseMessage = await client.PostAsync("/api/user/authenticate", content);
        var responseJson = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false); ;
        var jObject = JObject.Parse(responseJson);
        _TOKEN = jObject.GetValue("token").ToString();
        return _TOKEN;
     }catch
        [...]

Then using the token:

_connection = new HubConnectionBuilder().WithUrl(ApiCommunication._API_BASE_URI + "/network", options =>
{
  options.AccessTokenProvider = () => Task.FromResult(token);
}).Build();

So far so good. It's working as expected on my mobile app. But in order to make it work I had to set this piece of code on server side (Startup.cs):

services.AddAuthentication(options =>
{
   options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
   options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
 })
 .AddJwtBearer(x =>
 {
    x.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            ...

This prevents me for using cookie authentication anymore and therefore the mvc web app is no more working as expected as it's not able to get the current authenticated user amongs requests.

Removing the lines:

options .DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options .DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

makes the web app working correctly but not the mobile app anymore (hub calls fail due to Context.User.Identity.Name equals to null).

I've been searching all around about how to handle different schemes (in my case cookie + jwt) and from my understanding, this is by design not possible anymore.

Is there any possible workaround to use double scheme or am I missing something?

I thought maybe I shoud host 2 separate projects instead and use one with Cookie authentication and the other one with JWT?

Thanks in advance.

There are multiple ways to solve the issue you encounter, but first let's go through why it's not currently working.

What DefaultAuthenticateScheme means

When you set a value to the DefaultAuthenticateScheme property of AuthenticationOptions , you instruct the authentication middleware to try and authenticate every single HTTP request against that specific scheme. I'm going to assume that you're using ASP.NET Identity for cookie-based authentication, and when you call AddIdentity , it registers the cookie authentication scheme as the default one for authentication purposes; you can see this in the source code on GitHub .

However, it doesn't mean you can't use any other authentication scheme in your application.

The authorization system default policy

If all the protected endpoints of your application are meant to be accessible to clients authenticated with cookies or JWTs, one option is to use the authorization system default policy. That special policy is used when you use "empty" instances of the AuthorizeAttribute class — either as an attribute to decorate controllers/actions, or globally at the app level with a new AuthorizeFilter(new AuthorizeAttribute()) .

The default policy is set to only require an authenticated user, but doesn't define which authentication schemes need to be "tried" to authenticate the request. The result is that it relies on the authentication process already having been performed. It explains the behavior you're experiencing where only one of the 2 schemes works at a time.

We can change the default policy with a bit of code:

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-cookie-authentication-scheme", "your-jwt-authentication-scheme")
        .Build();
})

Specific authorization policies

If you find yourself in a situation where you require some endpoints to only be accessible to clients authenticated with cookies and others with JWTs, you can take advantage of authorization policies.

They work exactly like the default policy, expect you get to pick on an endpoint basis which one applies. You can add policies like so:

services.AddAuthorization(options =>
{
    options.AddPolicy("Cookies", new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-cookie-authentication-scheme")
        .Build());

    options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes("<your-jwt-authentication-scheme")
        .Build());
})

You can then refer to these policies in appropriate endpoints by decorating them with [Authorize(Policy = "<policy-name>")] . As a side note, if the only differentiator between your policies is the authentication scheme, it's possible to achieve the same result without creating policies, and referring to the appropriate authentication scheme(s) in [Authorize] attributes with the AuthenticationSchemes property.

Policies are valuable when you have more complex rules, like that specific claim needs this specific value , for example.

I hope this helps, let me know how you go! 👍

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM