Understand ASP Net core - Cookie based authentication

Note: This article is part of the understanding ASP.NET Core series. Please check the top blog or Click here to view the full-text catalog

summary

Usually, Authentication and Authorization are discussed together. However, because the two languages are similar in English and the words "Authentication and Authorization" are often used together, some readers who have just come into contact with this knowledge are confused, can not distinguish the difference between Authentication and Authorization, and even think that the two are the same. Therefore, I would like to give you a brief distinction between identity Authentication and Authorization.

identity authentication

Confirm who is performing the operation.

When a user requests a background service, the system first needs to know who the user is, Zhang San, Li Si or anonymous? The process of confirming identity is "identity authentication". In our real life, by showing your ID card, others can quickly confirm your identity.

to grant authorization

Confirm whether the operator has permission to perform the operation.

After confirming the identity, you have learned the user information, and then come to the authorization stage. At this stage, you need to confirm whether the user has permission to perform this operation, such as whether Zhang San has commodity viewing permission, editing permission, etc.

Cookie

Cookie is a familiar thing for many people. It is basically indispensable to be familiar with today's Web applications. If you don't know much about cookies, don't panic. I've sorted it out at the end of the article High quality articles , it is recommended that you continue to read the content below after you have an overall understanding of cookies!

For identity authentication based on cookies, the usual scheme is that after the user successfully logs in, the server will record the user's necessary information in the Cookie and send it to the browser. Later, when the user sends a request, the browser will send the Cookie back to the server, and the server can confirm the user's information through the information in the Cookie.

Before starting, in order to facilitate everyone to understand and operate, I have prepared a sample program. Please visit XXTk.Auth.Samples.Cookies.Web Get the source code. The code in the article is basically implemented in the sample program. It is strongly recommended to combine it!

Authentication

Add identity authentication Middleware

In ASP Net core, in order to authenticate, you need to add authentication Middleware - AuthenticationMiddleware through UseAuthentication in the HTTP request pipeline:

public class Startup
{
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();
    
        app.UseRouting();
    
        // Identity authentication Middleware
        app.UseAuthentication();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

UseAuthentication must be placed before UseEndpoints, otherwise the Controller cannot obtain identity information through HttpContext.

AuthenticationMiddleware simply confirms the user's identity. At the code level, it gives httpcontext For user assignment, please refer to the following code:

public class AuthenticationMiddleware
{
    private readonly RequestDelegate _next;

    public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
    {
        _next = next;
        Schemes = schemes;
    }

    public IAuthenticationSchemeProvider Schemes { get; set; }

    public async Task Invoke(HttpContext context)
    {
        // Record the original path and the original base path
        context.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
        {
            OriginalPath = context.Request.Path,
            OriginalPathBase = context.Request.PathBase
        });

        // If there is an explicitly specified identity authentication scheme, give priority to it (don't look here, just look below)
        var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
        foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
        {
            var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
            if (handler != null && await handler.HandleRequestAsync())
            {
                return;
            }
        }

        // Use the default authentication scheme for authentication, and assign httpcontext User
        var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
        if (defaultAuthenticate != null)
        {
            var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
            if (result?.Principal != null)
            {
                context.User = result.Principal;
            }
        }

        await _next(context);
    }
}

Configure Cookie authentication scheme

Now that the authentication middleware has been added, you need to add the services required for identity authentication in the ConfigureServices method and configure the authentication scheme.

We can add the services required for identity authentication through the AddAuthentication extension method, and optionally specify the name of the default authentication scheme, as an example:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

We added the service that authentication depends on and specified a service named cookieauthenticationdefaults The default authentication scheme of authenticationscheme is Cookies. Obviously, it is a Cookie based authentication scheme.

CookieAuthenticationDefaults is a static class that defines some common default values:

public static class CookieAuthenticationDefaults
{
    // Authentication scheme name
    public const string AuthenticationScheme = "Cookies";

    // Prefix of Cookie name
    public static readonly string CookiePrefix = ".AspNetCore.";
    
    // Login path
    public static readonly PathString LoginPath = new PathString("/Account/Login");

    // Logout path
    public static readonly PathString LogoutPath = new PathString("/Account/Logout");

    // Access denied path
    public static readonly PathString AccessDeniedPath = new PathString("/Account/AccessDenied");

    // return url parameter name
    public static readonly string ReturnUrlParameter = "ReturnUrl";
}

Now that we have specified the default authentication scheme, the next step is to configure the details of this scheme, followed by AddCookie:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                // The scheme is configured in detail here
            });
    }
}

Obviously, the first parameter of AddCookie is to specify the name of the authentication scheme, and the second parameter is detailed configuration.

Through options, you can perform detailed configuration for login, logout, cookies, etc. Its type is CookieAuthenticationOptions, which inherits from AuthenticationSchemeOptions. There are many attributes, so I'll choose some commonly used ones to explain.

In addition, because the configuration of options depends on the services in the DI container, the configuration of options has to be proposed from the AddCookie extension method.

Please review the following codes:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IDataProtectionProvider>((options, dp) =>
            {
                options.LoginPath = new PathString("/Account/Login");
                options.LogoutPath = new PathString("/Account/Logout");
                options.AccessDeniedPath = new PathString("/Account/AccessDenied");
                options.ReturnUrlParameter = "returnUrl";

                options.ExpireTimeSpan = TimeSpan.FromDays(14);
                //options.Cookie.Expiration = TimeSpan.FromMinutes(30);
                //options.Cookie.MaxAge = TimeSpan.FromDays(14);
                options.SlidingExpiration = true;
                
                options.Cookie.Name = "auth";
                //options.Cookie.Domain = ".xxx.cn";
                options.Cookie.Path = "/";
                options.Cookie.SameSite = SameSiteMode.Lax;
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                options.Cookie.IsEssential = true;
                options.CookieManager = new ChunkingCookieManager();
                
                options.DataProtectionProvider ??= dp;
                var dataProtector = options.DataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", CookieAuthenticationDefaults.AuthenticationScheme, "v2");
                options.TicketDataFormat = new TicketDataFormat(dataProtector);

                options.Events.OnSigningIn = context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} Logging in...");
                    return Task.CompletedTask;
                };

                options.Events.OnSignedIn = context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} Logged in");
                    return Task.CompletedTask;
                };
                
                options.Events.OnSigningOut = context =>
                {
                    Console.WriteLine($"{context.HttpContext.User.Identity.Name} cancellation");
                    return Task.CompletedTask;
                };

                options.Events.OnValidatePrincipal += context =>
                {
                    Console.WriteLine($"{context.Principal.Identity.Name} verification Principal");
                    return Task.CompletedTask;
                };
            });
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

Most of the above configurations use the default values of the program. Next, we will explain them in detail one by one:

  • LoginPath: the path of the login page, pointing to an Action.
    • The default is / Account/Login.
    • When the server does not allow anonymous access and needs to confirm the user information, jump to this page to log in.
    • In addition, the login method usually has a parameter called return url, which is used to automatically jump back to the previous page when the user logs in successfully. This parameter will also be automatically passed to the Action, which will be described in detail below.
  • LogoutPath: logout path, pointing to an Action. The default is / Account/Logout.
  • Accessdenied path: access denied page path, pointing to an Action. The default is / Account/AccessDenied. When Http status code 403 appears, it will jump to this page.
  • ReturnUrlParameter: the parameter name of the return url mentioned above, and the parameter value will be passed to the parameter through query. Default ReturnUrl.
  • ExpireTimeSpan: the validity period of the authentication ticket.
    • Default 14 days
    • In the code, the authentication ticket is represented as an object of authentication ticket type. It is like a handbag filled with items that can prove your identity, such as ID card, driver's license, etc.
    • The authentication ticket is stored in the Cookie, and its validity period is independent of the validity period of the Cookie. If the Cookie does not expire, but the authentication ticket expires, it cannot pass the authentication. When explaining the login section below, there is a detailed description of the validity period of the authentication bill.
  • Cookie.Expiration: the expiration time of the cookie, that is, the saving time in the browser, which is used to persist the cookie.
    • The Expires attribute in the corresponding Cookie is an explicit point in time.
    • It is currently disabled and we cannot assign a value to it.
  • Cookie.MaxAge: the expiration time of the cookie, that is, the saving time in the browser, which is used to persist the cookie.
    • The max age attribute in the corresponding Cookie is a time range.
    • If Max age and Expires of cookies are set at the same time, Max age shall prevail
    • If the Expires of the Cookie is not set, the Cookie If the value of maxage remains null, the validity period of the Cookie is the current Session. When the browser is closed, the Cookie will be cleared (in fact, some browsers have Session recovery function. When the browser is closed and reopened, the Cookie will be recovered as if the browser had never been closed).
  • SlidingExpiration: indicates whether the expiration method of the Cookie is sliding expiration. The default is true. In case of sliding expiration, if the server finds that the lifetime of the Cookie has exceeded half after receiving the request, the server will reissue a new Cookie, and the expiration time of the Cookie and the expiration time of the authentication ticket will be reset.
  • Cookie.Name: the name of the cookie. The default is AspNetCore.Cookies.
  • Cookie.Domain: the domain to which the cookie belongs, corresponding to the domain attribute of the cookie. Generally, "." At the beginning, allow subdomain s to be accessed. The default is the domain that requests the Url.
  • Cookie.Path: the path to which the cookie belongs, corresponding to the path property of the cookie. Default /.
  • Cookie.SameSite: set the mode to decide whether to carry cookies when sending requests across sites through the browser. There are three modes: None, Lax and Strict.
    public enum SameSiteMode
    {
        Unspecified = -1,
        None,
        Lax,
        Strict
    }
    
    • SameSiteMode.Unspecified: use the browser's default mode.
    • SameSiteMode.None: there is no restriction. Cookie s will be carried when sending the same site or cross site requests through the browser. This is a very deprecated mode and is vulnerable to CSRF attacks
    • SameSiteMode.Lax: default. Cookie s can be carried when sending the same site request or cross site partial GET request through the browser.
    • SameSiteMode.Strict: cookies will be carried only when the same site request is sent through the browser.
    • For more details, refer to the bottom Haowen recommendation
  • Cookie.HttpOnly: indicates whether the cookie can be accessed by client script (such as js). The default value is true, that is, client script access is prohibited, which can effectively prevent XSS attacks.
  • Cookie.SecurePolicy: sets the security policy of the cookie, corresponding to the Secure attribute of the cookie.
    public enum CookieSecurePolicy
    {
        SameAsRequest,
        Always,
        None
    }
    
    • Cookie SecurePolicy. Always: set Secure=true. The browser will send cookies to the server only when the login request and subsequent requests sent are Https.
    • Cookie SecurePolicy. None: Secure is not set, that is, when sending Http requests and Https requests, the browser will send cookies to the server.
    • CookieSecurePolicy.SameAsRequest: default value. Depending on the situation, if the login interface is an Https request, set Secure=true; otherwise, do not set.
  • Cookie.IsEssential: indicates that the cookie is necessary for the normal operation of the application and does not need to be used with the user's consent
  • Cookie Manager: Cookie manager, which is used to add response cookies, query request cookies, or delete cookies. The default is ChunkingCookieManager.
  • DataProtectionProvider: authentication ticket encryption and decryption provider, which can provide corresponding encryption and decryption tools on demand. The default is KeyRingBasedDataProtector. For information on data protection, refer to Official documents - ASP Net core data protection.
  • TicketDataFormat: the data format of the authentication ticket. The authentication ticket is encrypted and decrypted internally through the encryption and decryption tool provided by the DataProtectionProvider. The default is TicketDataFormat.

The following are some event callbacks:

  • Events.OnSigningIn: callback before login
  • Events.OnSignedIn: callback after login
  • Events.OnSigningOut: callback on logoff
  • Events.OnValidatePrincipal: callback when validating the Principal

If you don't think it's elegant to register callbacks in this way, you can inherit from CookieAuthenticationEvents to implement your own class and rewrite the corresponding methods internally, such as:

public class MyCookieAuthenticationEvents : CookieAuthenticationEvents {}

Finally, replace at options: options EventsType = typeof(MyCookieAuthenticationEvents);

  • Cross origin: compare the requested Url with the Url of the current page. If any one of the protocol, domain name and port number is different, it is regarded as cross domain.
  • Cross site: compared with cross domain, the rules of cross site are looser. The requested Url is compared with the Url of the current page. If eTLD + 1 is different, it is regarded as cross site.

Please refer to Understanding "same-site" and "same-origin"

User login and logout

User login

Now it's time for users to log in and log out. Remember, the login, logout and access forbidden paths configured in the scheme should correspond to the interface.

ASP.NET Core provides the extension method SignInAsync of HttpContext for login. We can use it for login. Only the Controller code is posted below. For the front-end code, please refer to the source code of github.

public class AccountController : Controller
{
    [HttpGet]
    public IActionResult Login([FromQuery] string returnUrl = null)
    {
        ViewBag.ReturnUrl = returnUrl;

        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Login([FromForm] LoginViewModel input)
    {
        ViewBag.ReturnUrl = input.ReturnUrl;

        // Login is considered successful if the user name and password are the same
        if (input.UserName != input.Password)
        {
            ModelState.AddModelError("UserNameOrPasswordError", "invalid username or password ");
        }

        if (!ModelState.IsValid)
        {
            return View();
        }

        var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
        identity.AddClaims(new[]
        {
            new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString("N")),
            new Claim(ClaimTypes.Name, input.UserName)
        });

        var principal = new ClaimsPrincipal(identity);

        // Sign in
        var properties = new AuthenticationProperties
        {
            IsPersistent = input.RememberMe,
            ExpiresUtc = DateTimeOffset.UtcNow.AddSeconds(60),
            AllowRefresh = true
        };
        await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, properties);

        if (Url.IsLocalUrl(input.ReturnUrl))
        {
            return Redirect(input.ReturnUrl);
        }

        return Redirect("/");
    }
}

First, let's talk about Claim, Identity and Principal:

  • Claim: a statement representing a piece of information. Take our ID card as an example, which contains name, gender and other information, such as "Name: Zhang San" and "gender: male", which are all claims.
  • Identity: indicates an identity. For a ClaimsIdentity, it consists of one or more claims. Our ID card is an identity.
  • Principal: indicates the user himself. For a ClaimsPrincipal, it consists of one or more claimsidentities. Think about it. Each of us has more than one identity. In addition to our ID card, we also have a driver's license, membership card, etc.

Back to the Login method, first declare an instance of ClaimsIdentity and set cookieauthenticationdefaults Authenticationscheme is passed in as an authentication type. It should be noted that this authentication type must not be null or empty string. Otherwise, under the default configuration, you will get the following error:

InvalidOperationException: SignInAsync when principal.Identity.IsAuthenticated is false is not allowed when AuthenticationOptions.RequireAuthenticatedSignIn is true.

Then, we store some non sensitive information of the user into ClaimsIdentity as a Claim, and finally put it into the ClaimsPrincipal instance.

In the SignInAsync extension method, we can configure authentication through AuthenticationProperties.

  • IsPersistent: whether the bill is persistent, that is, whether the Cookie where the bill is located is persistent. If persistent, the value of ExpiresUtc below will be set to the Expires property of the Cookie. The default is false.
  • ExpiresUtc: the expiration time of the ticket. The default value is null. If it is null, the Cookie authenticationhandler will set the Cookie authentication options in the Cookie authentication scheme configuration in the HandleSignInAsync method ExpireTimeSpan + AuthenticationProperties. The result of issuedutc is assigned to this property.
  • AllowRefresh: as mentioned above, in the Cookie authentication scheme configuration, the expiration method can be configured as sliding expiration. When the conditions are met, the Cookie will be reissued. In fact, to achieve this effect, you need to set AllowRefresh to null or true. The default is null.
  • IssuedUtc: issuing time of bill; null by default. Generally, no manual assignment is required. When it is null, CookieAuthenticationHandler will assign the current time to this property in the HandleSignInAsync method.

Here is a detailed description of the validity period of the certified Bill:

We have learned from the above that the validity period of the authenticated bill is through authenticationproperties It is set by expiresutc. It is a specific time point. If we do not assign this property manually, the Cookie authentication processor Cookie authenticationhandler will set the Cookie authentication options in the Cookie authentication scheme configuration ExpireTimeSpan + AuthenticationProperties. The result of issuedutc is assigned to this property.

We also know that when configuring Cookie authentication scheme, Cookie The expiration attribute indicates the Expires attribute of the Cookie, but it is disabled. If it is forcibly used, we will get an option verification error message:

Cookie.Expiration is ignored, use ExpireTimeSpan instead.

But the ExpireTimeSpan attribute, which is explicitly stated in the comment, does not refer to the Expires attribute of the Cookie, but the validity period of the ticket. What's the matter? In fact, you can imagine the following scenario: if the Expires and Max age of the Cookie are not set (the program allows them to be empty), the validity period of the Cookie is the current session. However, you can set authenticationproperties IsPersistent = true indicates that the Cookie is persistent, which leads to ambiguity. In fact, the Cookie is not persistent, but the code thinks it is persistent. So, in order to solve this ambiguity, Cookie Expiration is disabled, and an ExpireTimeSpan property is added, which can be used as the validity period of the ticket. In addition, expiration and Max age of the Cookie are not set, but authenticationproperties When isPersistent = true, set the value to the Expires property of the Cookie to make the Cookie persistent.

Let's take a look at the login effect:

  • When remember me is not selected:

  • When you select remember me:

Explore other features yourself!

The following is the core internal simulation of SignInAsync. For more details, please see AuthenticationService and CookieAuthenticationHandler:

public class AccountController : Controller
{
    private readonly IOptionsMonitor<CookieAuthenticationOptions> _cookieAuthOptionsMonitor;

    public AccountController(IOptionsMonitor<CookieAuthenticationOptions> cookieAuthOptions)
    {
        _cookieAuthOptionsMonitor = cookieAuthOptions;
    }

    [HttpPost]
    public async Task<IActionResult> Login([FromForm] LoginViewModel input)
    {
        // ...
        
        var options = _cookieAuthOptionsMonitor.Get(CookieAuthenticationDefaults.AuthenticationScheme);
        var ticket = new AuthenticationTicket(principal, properties, CookieAuthenticationDefaults.AuthenticationScheme);
        // ticket encryption
        var cookieValue = options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding(HttpContext));

        // CookieOptions is just new. In fact, the configuration of options and ticket should be transformed into CookieOptions
        options.CookieManager.AppendResponseCookie(HttpContext, options.Cookie.Name, cookieValue, new CookieOptions());

        // ...
    }
}

User logout

Logging out is relatively simple, that is, clear the cookies without repeating:

[HttpPost]
public async Task<IActionResult> Logout()
{
    await HttpContext.SignOutAsync();

    return Redirect("/");
}

You can see that the Cookie named "auth" has been emptied:

So far, a simple Cookie based identity authentication function has been realized.

Authorization

Add authorization Middleware

To use authorization, you need to add authorization middleware through UseAuthorization - AuthorizationMiddleware:

public class Startup
{
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();
    
        app.UseRouting();
    
        // Identity authentication Middleware
        app.UseAuthentication();
        // Authorization Middleware
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

UseAuthorization must be put after UseRouting and UseAuthentication, because the authorization middleware needs to use Endpoint. In addition, it should be placed before UseEndpoints, otherwise the authorization middleware will not be executed before the request reaches the Controller.

Authorization configuration

Now that the authorization middleware has been added, you need to add the services required for authorization in the ConfigureServices method and make additional configuration.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthorization(options =>
        {
            options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
            options.InvokeHandlersAfterFailure = true;
        });
    }
}
  • DefaultPolicy: the default authorization policy. The default is new authorizationpolicybuilder() RequireAuthenticatedUser(). Build (), that is, only users who have passed identity authentication can be authorized.
  • InvokeHandlersAfterFailure: when there are multiple authorized processors, if one fails, whether the subsequent processors will continue to execute. The default value is true, and execution will continue.

Url add authorization

Now, we require users to log in before accessing / Home/Privacy and add the [Authorize] feature. Instead of passing in the policy, we can use the default policy:

public class HomeController : Controller
{
    [HttpGet]
    [Authorize]
    public IActionResult Privacy()
    {
        return View();
    }
}

You can try to access httpcontext User, which is actually the ClaimsPrincipal created when we log in.

Global Cookie policy

In addition, we can globally configure Cookie policies through usecookie policy. It should be noted that CookiePolicyMiddleware only works on the middleware added after it, so try to put it in the front position.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Cookie global policy
        services.AddCookiePolicy(options =>
        {
            options.OnAppendCookie = context =>
            {
                Console.WriteLine("------------------ On Append Cookie --------------------");
                Console.WriteLine($"Name: {context.CookieName}\tValue: {context.CookieValue}");
            };

            options.OnDeleteCookie = context =>
            {
                Console.WriteLine("------------------ On Delete Cookie --------------------");
                Console.WriteLine($"Name: {context.CookieName}");
            };
        });

        services.AddControllersWithViews();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseStaticFiles();

        app.UseRouting();

        // Cookie policy Middleware
        app.UseCookiePolicy();

        // Identity authentication Middleware
        app.UseAuthentication();
        // Authorization Middleware
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

Optimization and improvement

Optimize Claim to reduce the size of authentication cookies

When the user logs in, Claims will be added after the authentication is passed. The "type" uses the ClaimTypes provided by Microsoft:

new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString("N")),
new Claim(ClaimTypes.Name, input.UserName)

Carefully, you will find that the value of ClaimTypes is too long:

public static class ClaimTypes
{
    public const string Name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name";

    public const string NameIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
}

We can use jwtcliimtypes for optimization:

public static class JwtClaimTypes
{
    public const string Id = "id";
    
    public const string Name = "name";
}
  1. Installing the IdentityModel package
Install-Package IdentityModel
  1. For replacement, note that you should specify the types of name and Role when creating the ClaimsIdentity instance, so that httpcontext User. Identity. Name and httpcontext User. IsInRole (string Role) can be used normally:
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, JwtClaimTypes.Name, JwtClaimTypes.Role);
identity.AddClaims(new[]
{
    new Claim(JwtClaimTypes.Id, Guid.NewGuid().ToString("N")),
    new Claim(JwtClaimTypes.Name, input.UserName)
});

Store Session information on the server

Perhaps, you still think that the volume of cookies is too large, and will become larger and larger with the increase of information stored in cookies. You can consider storing Session information on the server for solution, which also protects data security to a certain extent.

This scheme is very simple. We save the Session information, that is, the authentication ticket, on the server instead of a Cookie. Only one SessionId needs to be stored in the Cookie. When the request is sent to the server, the SessionId will be obtained. Through it, the complete Session information can be obtained from the server.

There are a variety of storage media for session information, including memory and distributed storage middleware, such as Redis. Next, I will take memory as an example (Redis's scheme can be found in my sample program source code, which will not be posted here).

In CookieAuthenticationOptions, there is a SessionStore of type ITicketStore, which is used to define the storage of sessions. Next, let's implement it:

public class MemoryCacheTicketStore : ITicketStore
{
    private const string KeyPrefix = "AuthSessionStore-";
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _defaultExpireTimeSpan;

    public MemoryCacheTicketStore(TimeSpan defaultExpireTimeSpan, MemoryCacheOptions options = null)
    {
        options ??= new MemoryCacheOptions();
        _cache = new MemoryCache(options);
        _defaultExpireTimeSpan = defaultExpireTimeSpan;
    }

    public async Task<string> StoreAsync(AuthenticationTicket ticket)
    {
        var guid = Guid.NewGuid();
        var key = KeyPrefix + guid.ToString("N");
        await RenewAsync(key, ticket);
        return key;
    }

    public Task RenewAsync(string key, AuthenticationTicket ticket)
    {
        var options = new MemoryCacheEntryOptions();
        var expiresUtc = ticket.Properties.ExpiresUtc;
        if (expiresUtc.HasValue)
        {
            options.SetAbsoluteExpiration(expiresUtc.Value);
        }
        else
        {
            options.SetSlidingExpiration(_defaultExpireTimeSpan);
        }

        _cache.Set(key, ticket, options);

        return Task.CompletedTask;
    }

    public Task<AuthenticationTicket> RetrieveAsync(string key)
    {
        _cache.TryGetValue(key, out AuthenticationTicket ticket);
        return Task.FromResult(ticket);
    }

    public Task RemoveAsync(string key)
    {
        _cache.Remove(key);
        return Task.CompletedTask;
    }
}

Then, just give cookieauthenticationoptions Just assign a value to sessionstore:

options.SessionStore = new MemoryCacheTicketStore(options.ExpireTimeSpan);

The following is an example of SessionId stored in a Cookie. Although it is still very long, it will not grow with the increase of the amount of information:

CfDJ8OGRqoEUgBZEu4m5Q8NfuATXjRKivKy7CR-oPpx2SaNJ8n1GWyBbPhNTEQzzIbZ62DqJPuxKtBJ752GqNxod9U5paaI_aQdH9EOH8nvgrinjvdHTneeKlhBvamEQrq7nA1e3wJOuQwFXRJASUphkS3kQzvc4-Upz27AAfoD510MC7YiwlhyxWl7agb8F0eeiilxAHDn4gskVqshu2hc5ENQAJNjXpa0yVaseryvsPrbukv5jqGC12WuUVe1cYhBIdWHHT61ZJcNtvNOAdtVlVA7i7RCJUBxNCUAhB-mw_s7R4GsNbU8aW7Ye9H-tx5067w

Haowen recommendation

Please stamp the source code XXTk.Auth.Samples.Cookies.Web

Keywords: ASP.NET .NET cookie

Added by academ1c on Tue, 18 Jan 2022 17:23:55 +0200