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:
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:
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():
AddIdentity():
AddDefaultIdentity():
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:
In the next article we will cover the Identity Flows mentioned above.
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.
Since 2008 Experienced Electronics Solutions and Services | Optimize Electronic Components Supply Needs
11 个月Absolutely!?Thanks for sharing!