ASP.NET Identity Framework

ASP.NET Identity Framework

The ASP.NET membership system was introduced with ASP.NET 2.0 back in 2005, and since then there have been many changes in the ways web applications typically handle authentication and authorization. ASP.NET Identity is a fresh look at what the membership system should be when you are building modern applications for the web, phone, or tablet.

The ASP.NET Core Identity is:

  • Is an API that supports user interface (UI) login functionality.
  • Manages users, passwords, profile data, roles, claims, tokens, email confirmation, and more.

Users can create an account with the login information stored in Identity or they can use an external login provider.

Identity is typically configured using a SQL Server database to store user names, passwords, and profile data. Alternatively, another persistent store can be used

In this article, you learn how to use Identity to register, log in, and log out a user. Note: the templates treat username and email as the same for users. If you want a post to cover that, leave a comment.

This article will not get in each detail or step-by-step, I'll cover just topics that will step up your knowledge about identity, so I'll not cover how to create a new solution, I'll make a assumption that you already have your solution created. If that is you want me to cover, just leave a comment.

With theses things out of way, the first topic is:

  1. How to add ASP.Identity in my project

To add Identity in your project, we have three service collection extension methods: AddIdentity(), AddDefaultIdentity() and AddIdentityCore()

I'll not enter into details here, you can use each one of them as you like, just keep in mind:

AddIdentityCore():

  • ?Is the most minimal way of adding authentication and authorization services to your application. It provides a minimal set of options and is intended for advanced scenarios where you want to have a clean starting point to configure Authentication and Authorization further yourself (assuming you will need further modifications to add more services, etc). It does NOT add some services such as Cookie Schemes, SignInManager, or RoleManager. Source code.

AddIdentity():

  • Adds everything?AddIdentityCore()?adds, with some extra services, namely Cookie Schemes (Application, External, and 2FA Schemes are all registered), SignInManager, and RoleManager. Note that?AddIdentity()?adds Role services automatically, unlike?AddDefaultIdentity(), but don′t add the default UI. Source code.

AddDefaultIdentity():

  • Comes with a lot of pre-configured options that are suitable for most applications, and it also calls "AddDefaultUI()" to add the default UI components under the "Identity" area. It is suitable for scenarios where you want to get up and running quickly with a reasonable set of defaults and a working UI. Source code.

Note that?AddDefaultIdentity()?does NOT add Role services, so if you want to use?AddDefaultIdentity()?and also want to have Role services to your application, you must call "AddRoles" explicitly with it.

Note:?AddRoles()?must be added BEFORE AddEntityFrameworkStores(), otherwise you will get errors and it will be hard to figure out why.

2. Interesting customizations

2.1 Change the key type

If you use the standard code, as is, your identity objects will be generated with an Int key, if you want to change this, just creating a new IdentityUser object, like the code below:

public class ApplicationUser : IdentityUser<Guid>
{
}        

In the example above, our key will be the type of Guid, you can add more custom fields here to extend the User, but i recommend to let this to an Profile Service, here should be only the information needed to make the login/logout process.

Likewise if you want to change the key type for the Role, you can change in a similar way.

Other change is that, you need to register with this new class, like below:

builder.Services.AddDefaultIdentity<ApplicationUser>()
                .AddRoles<ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();        

Just remember to use correctly the .AddIdentity, .AddDefaultIdentity e .AddIdentityCore.

For example with .AddIdentity could be something like:

builder.Services.AddIdentity<ApplicationUser, ApplicationRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultUI()
                .AddDefaultTokenProviders();        

2.2 Configuring options

There is some interesting configurations that you can change on identity, the changing the key it's something you should think to do, theses are more optional's:

builder.Services.Configure<IdentityOptions>(options =>
{
      options.Lockout.AllowedForNewUsers = true;
      options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
      options.Lockout.MaxFailedAccessAttempts = 5;
      options.Password.RequireDigit = true;
      options.Password.RequireLowercase = true;
      options.Password.RequireNonAlphanumeric = true;
      options.Password.RequireUppercase = true;
      options.Password.RequiredLength = 6;
      options.Password.RequiredUniqueChars = 1;
      options.SignIn.RequireConfirmedAccount = false;
      options.SignIn.RequireConfirmedEmail = false;
      options.SignIn.RequireConfirmedPhoneNumber = false;
      options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
      options.User.RequireUniqueEmail = false;
});        

The options shown is the code above are that is interesting to change with it's default values, there is other options like: ClaimsIdentity, Stores and Tokens, i don′t recommend to change if you don′t know why you should change.

2.3 Cookies configurations

There is some cookies configurations that you maybe want to change, like:

builder.Services.ConfigureApplicationCookie(options =
{
    options.Cookie.Domain = "www.yourdomain.com";
??? options.Cookie.Expiration = TimeSpan.FromMinutes(60);
??? options.Cookie.HttpOnly = true;
??? options.Cookie.IsEssential = false;
??? options.Cookie.Name = "Identity.Application";
??? options.Cookie.SameSite = SameSiteMode.Unspecified;
??? options.Cookie.SecurePolicy = CookieSecurePolicy.None;
??? options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
??? options.AccessDeniedPath = "/Identity/Account/AccessDenied";
??? options.LoginPath = "/Identity/Account/Login";
??? options.LogoutPath = "/Identity/Account/Logout";
});        

Just be sure ConfigureApplicationCookie is called after calling AddIdentity or AddDefaultIdentity.

3. Using Token instead of Cookies

Let′s now change from cookie authentication to token authentication, instead of the step 2.3, we are going to make some changes for supporting the use of the token instead of cookies, first step will be add a Session, this will be used to store our token, and will be implemented as a cookie at the end.

builder.Services.AddSession(options =
{
??? options.Cookie.Domain = "localhost";
??? options.IdleTimeout = TimeSpan.FromSeconds(100);
??? options.Cookie.HttpOnly = true;
??? options.Cookie.IsEssential = true;
});        

After this, we need just to make sure everything is clear, we will clear the claim type map. You will need to add an reference to System.IdentityModel.Tokens.Jwt.

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();        

After that we will need to change the configurations of the Authentication to use the JWT token, you will need the references: Microsoft.AspNetCore.Authentication.JwtBearer and Microsoft.IdentityModel.Tokens;

var jwtConfig = builder.Configuration.GetSection("jwt");

builder.Services.AddAuthentication(o =>
??? {
??????? o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
??????? o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
??????? o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
??????? o.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
??? }).AddJwtBearer(options =>
??? {
??????? options.Events = new JwtBearerEvents()
??????? {
??????????? OnAuthenticationFailed = c =>
??????????? {
??????????????? c.Response.StatusCode = 500;
??????????????? c.Response.ContentType = "text/plain";
??????????????? if (builder.Environment.IsDevelopment())
??????????????? {
??????????????????? // Debug only, in production do not share exceptions with the remote host.
??????????????????? return c.Response.WriteAsync(c.Exception.ToString());
??????????????? }
??????????????? return c.Response.WriteAsync("An error occurred processing your authentication.");
??????????? }
??????? };
?????? ?
??????? options.RequireHttpsMetadata = false;
??????? options.SaveToken = true;
??????? options.TokenValidationParameters = new TokenValidationParameters
??????? {
??????????? SaveSigninToken = true,
??????????? ValidateIssuer = false,
??????????? ValidateAudience = false,
??????????? ValidateLifetime = true,
??????????? ValidateIssuerSigningKey = true,
??????????? ValidIssuer = jwtConfig["ValidIssuer"],
??????????? ValidAudience = jwtConfig["ValidAudience"],
??????????? IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig["key"])),
??????????? ClockSkew = TimeSpan.Zero
??????? };
??? });        

The appsettings piece that you need:

"jwt": 
??? "ValidIssuer": "https://localhost",
??? "ValidAudience": "YourAudience",
??? "key": "useAStrongKey"
? },{        

The big problem is, our token is stored in the Session, so we need manually get from there in each requests, so for that we need to add

app.Use(async (context, next) =
{
??? var token = context.Session.GetString("Token");
??? if (!string.IsNullOrEmpty(token))
??? {
??????? if (string.IsNullOrEmpty(context.Response.Headers.Authorization))
??????? {
??????????? context.Request.Headers.Add("Authorization", "Bearer " + token);
??????? }
??? }
??? await next();
});        

Other thing that was not mentioned until now is that you need to add some services

builder.Services.AddTransient<ITokenService, TokenService>()
builder.Services.AddHttpContextAccessor();;        

The token service can be something like:

public class TokenService : ITokenServic
??? {
??????? private const double ExpiryDurationMinutes = 30;

??????? public string BuildToken(string key, string issuer, ApplicationUser user)
??????? {
??????????? var claims = new[] {
??????????????? new Claim(ClaimTypes.Name, user.UserName),
??????????????? new Claim(ClaimTypes.Role, user.Role),
??????????????? new Claim(ClaimTypes.NameIdentifier,
??????????????????? user.Id.ToString())
??????????? };

??????????? var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
??????????? var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
??????????? var tokenDescriptor = new JwtSecurityToken(issuer, issuer, claims,
??????????????? expires: DateTime.Now.AddMinutes(ExpiryDurationMinutes), signingCredentials: credentials);

??????????? return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
??????? }

??????? public ClaimsIdentity GenerateClaims(ApplicationUser user)
??????? {
??????????? var claims = new[] {
??????????????? new Claim(ClaimTypes.Name, user.UserName),
??????????????? new Claim(ClaimTypes.Role, user.Role),
??????????????? new Claim(ClaimTypes.NameIdentifier,
??????????????????? user.Id.ToString())
??????????? };

??????????? var claim = new ClaimsIdentity(claims);

??????????? return claim;
??????? }
??????? public bool IsTokenValid(string key, string issuer, string token)
??????? {
??????????? var mySecret = Encoding.UTF8.GetBytes(key);
??????????? var mySecurityKey = new SymmetricSecurityKey(mySecret);
??????????? var tokenHandler = new JwtSecurityTokenHandler();
??????????? try
??????????? {
??????????????? tokenHandler.ValidateToken(token,
??????????????????? new TokenValidationParameters
??????????????????? {
??????????????????????? ValidateIssuerSigningKey = true,
??????????????????????? ValidateIssuer = true,
??????????????????????? ValidateAudience = true,
??????????????????????? ValidIssuer = issuer,
??????????????????????? ValidAudience = issuer,
??????????????????????? IssuerSigningKey = mySecurityKey,
??????????????????? }, out SecurityToken validatedToken);
??????????? }
??????????? catch
??????????? {
??????????????? return false;
??????????? }
??????????? return true;
??????? }
??? }        

I don′t recommend to use the authentication with JWT token in a real application because you will be passing account information in a very risky way, to avoid that i store and send the token in a session cookie, i could encrypt the token too, but will take this article more extensive. Another thing that is not good is the amount of unnecessary custom code just to make things works, for example the add the token from the session to Authorization header, this is due the [Authorize] that you need to change to [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] will require out of the box the authorization in the header, and to change this will be more unnecessary custom code.

In a real scenario, you need to use an HTTP Only Cookie that will be not accessible by the browser, if you need two different applications have access to the same Authentication info, you can share the cryptography keys of the cookie setting up the Data Protection, shown below the code:

builder.Services.AddDataProtection()
    .SetApplicationName("<sharedApplicationName>");        

Of course you need to choose where the keys are stored, for example if you want to store into a file:

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(@"\directory\"))
    .SetApplicationName("<sharedApplicationName>");
        

With this setup in both applications (considering both are .NET application) both will be able to see the user is logged in.

This article cover all the setup need to be done to add the .NET Identity Framework into the application, but there is some flows related to the identity as:

  • Registration/Confirmation
  • Login/Logout
  • Forgot Password/ Recover Password
  • Manage Account

In the next article we will cover the Identity Flows mentioned above.

Stefan Xhunga

CEO | Kriselaengineering | Sales Certified - Software as a Service Solutions

11 个月

@ ? Implementing authentication in ? ASP.NET Identity is crucial for safeguarding ? user accounts, enhancing security, and ?? improving the overall user experience. ? By actively engaging in the discussion and providing feedback, we can collectively ensure a comprehensive understanding of the code implementation and address any missing or unusual aspects.

MYRA Huang

Since 2008 Experienced Electronics Solutions and Services | Optimize Electronic Components Supply Needs

11 个月

Absolutely!?Thanks for sharing!

要查看或添加评论,请登录

Luigi C. Filho的更多文章

社区洞察

其他会员也浏览了