اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
نویسنده: وحید نصیری
تاریخ: ۱۳۹۶/۰۶/۰۸ ۲۰:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
public void ConfigureServices(IServiceCollection services)
{
services.Configure<BearerTokensOptions>(options => Configuration.GetSection("BearerTokens").Bind(options));
services
.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["BearerTokens:Issuer"],
ValidAudience = Configuration["BearerTokens:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["BearerTokens:Key"])),
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
cfg.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
logger.LogError("Authentication failed.", context.Exception);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>();
return tokenValidatorService.ValidateAsync(context);
},
OnMessageReceived = context =>
{
return Task.CompletedTask;
},
OnChallenge = context =>
{
var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
logger.LogError("OnChallenge error", context.Error, context.ErrorDescription);
return Task.CompletedTask;
}
};
}); {
"BearerTokens": {
"Key": "This is my shared key, not so secret, secret!",
"Issuer": "http://localhost/",
"Audience": "Any",
"AccessTokenExpirationMinutes": 2,
"RefreshTokenExpirationMinutes": 60
}
} [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
$.ajax({
headers: { 'Authorization': 'Bearer ' + jwtToken }, const string tokenKey = "my.custom.jwt.token.key";
if (context.HttpContext.Items.ContainsKey(tokenKey))
{
context.Token = (string)context.HttpContext.Items[tokenKey];
} OnTokenValidated = context =>
{
var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenValidatorService>();
return tokenValidatorService.ValidateAsync(context);
}, public class TokenValidatorService : ITokenValidatorService
{
private readonly IUsersService _usersService;
private readonly ITokenStoreService _tokenStoreService;
public TokenValidatorService(IUsersService usersService, ITokenStoreService tokenStoreService)
{
_usersService = usersService;
_usersService.CheckArgumentIsNull(nameof(usersService));
_tokenStoreService = tokenStoreService;
_tokenStoreService.CheckArgumentIsNull(nameof(_tokenStoreService));
}
public async Task ValidateAsync(TokenValidatedContext context)
{
var userPrincipal = context.Principal;
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
{
context.Fail("This is not our issued token. It has no claims.");
return;
}
var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber);
if (serialNumberClaim == null)
{
context.Fail("This is not our issued token. It has no serial.");
return;
}
var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value;
if (!int.TryParse(userIdString, out int userId))
{
context.Fail("This is not our issued token. It has no user-id.");
return;
}
var user = await _usersService.FindUserAsync(userId).ConfigureAwait(false);
if (user == null || user.SerialNumber != serialNumberClaim.Value || !user.IsActive)
{
// user has changed his/her password/roles/stat/IsActive
context.Fail("This token is expired. Please login again.");
}
var accessToken = context.SecurityToken as JwtSecurityToken;
if (accessToken == null || string.IsNullOrWhiteSpace(accessToken.RawData) ||
!await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId).ConfigureAwait(false))
{
context.Fail("This token is not in our database.");
return;
}
await _usersService.UpdateUserLastActivityDateAsync(userId).ConfigureAwait(false);
}
} public class UserToken
{
public int Id { get; set; }
public string AccessTokenHash { get; set; }
public DateTimeOffset AccessTokenExpiresDateTime { get; set; }
public string RefreshTokenIdHash { get; set; }
public DateTimeOffset RefreshTokenExpiresDateTime { get; set; }
public int UserId { get; set; } // one-to-one association
public virtual User User { get; set; }
} public interface ITokenStoreService
{
Task AddUserTokenAsync(UserToken userToken);
Task AddUserTokenAsync(
User user, string refreshToken, string accessToken,
DateTimeOffset refreshTokenExpiresDateTime, DateTimeOffset accessTokenExpiresDateTime);
Task<bool> IsValidTokenAsync(string accessToken, int userId);
Task DeleteExpiredTokensAsync();
Task<UserToken> FindTokenAsync(string refreshToken);
Task DeleteTokenAsync(string refreshToken);
Task InvalidateUserTokensAsync(int userId);
Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user);
} private async Task<string> createAccessTokenAsync(User user, DateTime expires)
{
var claims = new List<Claim>
{
// Unique Id for all Jwt tokes
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
// Issuer
new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer),
// Issued at
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim("DisplayName", user.DisplayName),
// to invalidate the cookie
new Claim(ClaimTypes.SerialNumber, user.SerialNumber),
// custom data
new Claim(ClaimTypes.UserData, user.Id.ToString())
};
// add roles
var roles = await _rolesService.FindUserRolesAsync(user.Id).ConfigureAwait(false);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _configuration.Value.Issuer,
audience: _configuration.Value.Audience,
claims: claims,
notBefore: DateTime.UtcNow,
expires: expires,
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
} <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.0.0" /> </ItemGroup>
public async Task<(string accessToken, string refreshToken)> CreateJwtTokens(User user)
{
var now = DateTimeOffset.UtcNow;
var accessTokenExpiresDateTime = now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes);
var refreshTokenExpiresDateTime = now.AddMinutes(_configuration.Value.RefreshTokenExpirationMinutes);
var accessToken = await createAccessTokenAsync(user, accessTokenExpiresDateTime.UtcDateTime).ConfigureAwait(false);
var refreshToken = Guid.NewGuid().ToString().Replace("-", "");
await AddUserTokenAsync(user, refreshToken, accessToken, refreshTokenExpiresDateTime, accessTokenExpiresDateTime).ConfigureAwait(false);
await _uow.SaveChangesAsync().ConfigureAwait(false);
return (accessToken, refreshToken);
} [AllowAnonymous]
[HttpPost("[action]")]
public async Task<IActionResult> Login([FromBody] User loginUser)
{
if (loginUser == null)
{
return BadRequest("user is not set.");
}
var user = await _usersService.FindUserAsync(loginUser.Username, loginUser.Password).ConfigureAwait(false);
if (user == null || !user.IsActive)
{
return Unauthorized();
}
var (accessToken, refreshToken) = await _tokenStoreService.CreateJwtTokens(user).ConfigureAwait(false);
return Ok(new { access_token = accessToken, refresh_token = refreshToken });
} [AllowAnonymous]
[HttpPost("[action]")]
public async Task<IActionResult> RefreshToken([FromBody]JToken jsonBody)
{
var refreshToken = jsonBody.Value<string>("refreshToken");
if (string.IsNullOrWhiteSpace(refreshToken))
{
return BadRequest("refreshToken is not set.");
}
var token = await _tokenStoreService.FindTokenAsync(refreshToken);
if (token == null)
{
return Unauthorized();
}
var (accessToken, newRefreshToken) = await _tokenStoreService.CreateJwtTokens(token.User).ConfigureAwait(false);
return Ok(new { access_token = accessToken, refresh_token = newRefreshToken });
} [AllowAnonymous]
[HttpGet("[action]"), HttpPost("[action]")]
public async Task<bool> Logout()
{
var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userIdValue = claimsIdentity.FindFirst(ClaimTypes.UserData)?.Value;
// The Jwt implementation does not support "revoke OAuth token" (logout) by design.
// Delete the user's tokens from the database (revoke its bearer token)
if (!string.IsNullOrWhiteSpace(userIdValue) && int.TryParse(userIdValue, out int userId))
{
await _tokenStoreService.InvalidateUserTokensAsync(userId).ConfigureAwait(false);
}
await _tokenStoreService.DeleteExpiredTokensAsync().ConfigureAwait(false);
await _uow.SaveChangesAsync().ConfigureAwait(false);
return true;
} {"error":{"code":"UnsupportedApiVersion","message":"The HTTP resource that matches the request URI 'http://localhost:45225/api/account/login?ReturnUrl=%2Fidentity%2Fhome' does not support HTTP method 'GET'."}} new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64),
Its value MUST be a number containing a NumericDate value.
A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap seconds. This is equivalent to the IEEE Std 1003.1, 2013 Edition [POSIX.1] definition "Seconds Since the Epoch", in which each day is accounted for by exactly 86400 seconds, other than that non-integer values can be represented. See RFC 3339 [RFC3339] for details regarding date/times in general and UTC in particular.
DateTimeOffset.UtcNow.ToUnixTimeSeconds()
{"jti":"26bdfd20-104f-45d4-a4e1-111044808041",
"iss":"http://localhost:5000/",
"iat":1531729854,
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier":"1",
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"Vahid",
"DisplayName":"وحید",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber":"046fb152a7474043952475cfa952cdc9",
"http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata":"1",
"DynamicPermission":[":MyProtectedApi2:Get",
":MyProtectedEditorsApi:Get",
":MyProtectedApi3:Get",
":MyProtectedApi4:Get"],
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role":["Admin",
"Editor",
"User"],
"nbf":1531729855,
"exp":1531729975,
"aud":"Any"} public bool CanUserAccess(ClaimsPrincipal user, string area, string controller, string action)
{
var currentClaimValue = $"{area}:{controller}:{action}";
var securedControllerActions = _mvcActionsDiscoveryService.GetAllSecuredControllerActionsWithPolicy(ConstantPolicies.DynamicPermission);
if (!securedControllerActions.SelectMany(x => x.MvcActions).Any(x => x.ActionId == currentClaimValue))
{
throw new KeyNotFoundException($@"The `secured` area={area}/controller={controller}/action={action} with `ConstantPolicies.DynamicPermission` policy not found. Please check you have entered the area/controller/action names correctly and also it's decorated with the correct security policy.");
}
if (!user.Identity.IsAuthenticated)
{
return false;
}
if (user.IsInRole("Admin"))
{
// Admin users have access to all of the pages.
return true;
}
// Check for dynamic permissions
// A user gets its permissions claims from the `ApplicationClaimsPrincipalFactory` class automatically and it includes the role claims too.
//for check user has claim for access to action
return user.HasClaim(claim => claim.Type == ConstantPolicies.DynamicPermissionClaimType &&
claim.Value == currentClaimValue);
} private async Task<(string AccessToken, IEnumerable<Claim> Claims)> createAccessTokenAsync(User user)
{
var claims = new List<Claim>
{
// Unique Id for all Jwt tokes
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
// Issuer
new Claim(JwtRegisteredClaimNames.Iss, _configuration.Value.Issuer, ClaimValueTypes.String, _configuration.Value.Issuer),
// Issued at
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64, _configuration.Value.Issuer),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer),
new Claim(ClaimTypes.Name, user.Username, ClaimValueTypes.String, _configuration.Value.Issuer),
new Claim("DisplayName", user.DisplayName, ClaimValueTypes.String, _configuration.Value.Issuer),
// to invalidate the cookie
new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, _configuration.Value.Issuer),
// custom data
new Claim(ClaimTypes.UserData, user.Id.ToString(), ClaimValueTypes.String, _configuration.Value.Issuer)
};
// add userclaims for permission
var clmsuser= await _rolesService.FindUserClaimesAsync(user.Id);
foreach (var cls in clmsuser)
{
claims.Add(new Claim(cls.ClaimType,cls.ClaimValue,ClaimValueTypes.String,_configuration.Value.Issuer));
}
// add roles
var roles = await _rolesService.FindUserRolesAsync(user.Id);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, _configuration.Value.Issuer));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var now = DateTime.UtcNow;
var token = new JwtSecurityToken(
issuer: _configuration.Value.Issuer,
audience: _configuration.Value.Audience,
claims: claims,
notBefore: now,
expires: now.AddMinutes(_configuration.Value.AccessTokenExpirationMinutes),
signingCredentials: creds);
return (new JwtSecurityTokenHandler().WriteToken(token), claims);
} Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired. ValidTo: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]', Current time: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. at Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__6.MoveNext()
« ... حالا بعد از اعمال روش ارائه شده در این مطلب (ذخیرهسازی token و refresh token در دیتابیس) چطور میتوانیم کاربرانی که از توکن قبلی استفاده میکنند را مجبور به Sign out کنیم؟ ...»همان قسمت «تهیه یک اعتبارسنج توکن سفارشی» مطلب جاری هست که از نتیجهی «پیاده سازی Logout» استفاده میکند. یا حتی میتوانید در قسمت logout یک SerialNumber را هم تغییر دهید که به صورت یک Claim سفارشی در توکن قبلی وجود داشته باشد. عدم انطباق این مقادیر را در این اعتبارسنج سفارشی بررسی کنید.
Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired. ValidTo: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]', Current time: '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. at Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__6.MoveNext()
Response status code does not indicate success: 500 (Internal Server Error)
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://localhost:45225/api/account/login application/json; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 POST http://localhost:45225/api/account/login application/json; charset=utf-8
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Login", controller = "Account", area = ""}. Executing action DrugExchange.Controllers.AccountController.Login (DrugExchange)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Login", controller = "Account", area = ""}. Executing action DrugExchange.Controllers.AccountController.Login (DrugExchange)
Microsoft.EntityFrameworkCore.Infrastructure:Information: Entity Framework Core 2.1.1-rtm-30846 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: MaxPoolSize=128 CommandTimeout=180
Microsoft.EntityFrameworkCore.Infrastructure:Information: Entity Framework Core 2.1.1-rtm-30846 initialized 'ApplicationDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: MaxPoolSize=128 CommandTimeout=180
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'DrugExchange.Common.ApiError'.
Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor:Information: Executing ObjectResult, writing value of type 'DrugExchange.Common.ApiError'.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action DrugExchange.Controllers.AccountController.Login (DrugExchange) in 42.4741ms
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action DrugExchange.Controllers.AccountController.Login (DrugExchange) in 42.4741ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 62.5345ms 500 application/ion+json; charset=utf-8
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 62.5345ms 500 application/ion+json; charset=utf-8
Microsoft.AspNetCore.Server.Kestrel:Information: Connection id "0HLFGFSKPQEQC", Request id "0HLFGFSKPQEQC:00000001": the application completed without reading the entire request body.
Microsoft.AspNetCore.Server.Kestrel:Information: Connection id "0HLFGFSKPQEQC", Request id "0HLFGFSKPQEQC:00000001": the application completed without reading the entire request body.
MaxRequestBytes 16384large_client_header_buffers 4 * 8k = 32768maxHttpHeaderSize 8192 public IActionResult Get()
{
//return Ok(new
//{
// Id = 1,
// Title = "Hello from My Protected Controller! [Authorize]",
// Username = this.User.Identity.Name
//});
return View();
} Response.Headers.Add("AccessToken", accessToken);
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.....
}); services.AddAuthorization(options =>
{
options.AddPolicy("Over18", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
policy.Requirements.Add(new MinimumAgeRequirement());
});
}); [Authorize(Policy = "Over18")]
[Authorize(Policy = ...., AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] // Or [Authorize(Policy = ...., AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme + ", " + JwtBearerDefaults.AuthenticationScheme)]
RefreshTokenIdHashSource = string.IsNullOrWhiteSpace(refreshTokenSourceSerial) ?
null : _securityService.GetSha256Hash(refreshTokenSourceSerial),
"AllowMultipleLoginsFromTheSameUser": false, "AllowSignoutAllUserActiveClients": true
AccessTokenExpiresDateTime
"AccessTokenExpirationMinutes": 2, "RefreshTokenExpirationMinutes": 1,
OnTokenValidated = async ctx =>
{
string oid = ctx.Principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
//Get EF context
var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>();
//Check is user a super admin
bool isSuperAdmin = await db.SuperAdmins.AnyAsync(a => a.ObjectId == oid);
if (isSuperAdmin)
{
//Add claim if they are
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "superadmin")
};
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
} All the existing claims will still be there after this. We are only adding new claims
services.AddAntiforgery (x => x.HeaderName = "X-XSRF-TOKEN");
services.AddMvc (options => { options.Filters.Add (new
AutoValidateAntiforgeryTokenAttribute ()); }); services.AddAuthorization(options =>
{
options.AddPolicy(CustomRoles.Admin, policy => policy.RequireRole(CustomRoles.Admin));
options.AddPolicy(CustomRoles.User, policy => policy.RequireRole(CustomRoles.User));
options.AddPolicy(CustomRoles.Editor, policy => policy.RequireRole(CustomRoles.Editor));
});
// Needed for jwt auth.
services
.AddAuthentication(options =>
{
options.DefaultChallengeScheme = siteSettings.JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = siteSettings.JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = siteSettings.JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["BearerTokens:Issuer"], // site that makes the token
ValidateIssuer = false, // TODO: change this to avoid forwarding attacks
ValidAudience = Configuration["BearerTokens:Audience"], // site that consumes the token
ValidateAudience = false, // TODO: change this to avoid forwarding attacks
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["BearerTokens:Key"])),
ValidateIssuerSigningKey = true, // verify signature to avoid tampering
ValidateLifetime = true, // validate the expiration
ClockSkew = TimeSpan.Zero // tolerance for the expiration date
}; | آخرین نگارش نیوگت موجود | اسمبلی مرتبط | متد الحاقی |
| 2.2.0 | Microsoft.AspNetCore.Authorization.Policy, Version=3.1.0.0 | ()AddAuthorization |
| 2.2.0 | Microsoft.AspNetCore.Authentication, Version=3.1.0.0 | ()AddAuthentication |
| 3.1.3 | Microsoft.AspNetCore.Authentication.JwtBearer, Version=3.1.2.0 | ()AddJwtBearer |
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project> Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
$.ajax({
headers: { 'Authorization': 'Bearer ' + jwtToken }, ... var token = await _tokenStoreService.FindTokenAsync(refreshTokenValue); ...
... var refreshTokenSerial = _tokenFactoryService.GetRefreshTokenSerial(refreshTokenValue); ...
...
decodedRefreshTokenPrincipal = new JwtSecurityTokenHandler().ValidateToken(
refreshTokenValue,
new TokenValidationParameters
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Value.Key)),
ValidateIssuerSigningKey = true, // verify signature to avoid tampering
ValidateLifetime = true, // validate the expiration
ClockSkew = TimeSpan.Zero // tolerance for the expiration date
},
out _
);
... اینجا قسمتی هست که درخواست سمت api ارسال میشه. ممنون میشم بهم بگین که کجا ی کار ایراد داره!
var claimsIdentity = this.User.Identity as ClaimsIdentity ;
public class TokenFactoryService
{
private readonly JwtBearerOptions _jwtBearerOptions;
public TokenFactoryService(IOptionsSnapshot<JwtBearerOptions> jwtBearerOptions)
{
if (jwtBearerOptions == null)
{
throw new ArgumentNullException(nameof(jwtBearerOptions));
}
_jwtBearerOptions = jwtBearerOptions.Value ?? throw new ArgumentNullException(nameof(jwtBearerOptions));
}
// From: https://github.com/dotnet/aspnetcore/blob/a450cb69b5e4549f5515cdb057a68771f56cefd7/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs
public bool ValidateJwt(string token)
{
foreach (var validator in _jwtBearerOptions.SecurityTokenValidators)
{
try
{
if (validator.CanReadToken(token))
{
validator.ValidateToken(token, _jwtBearerOptions.TokenValidationParameters, out _);
}
}
catch
{
return false;
}
}
return true;
}
} services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://localhost:4983/";
options.Audience = "Any";
options.RequireHttpsMetadata = false;
options.Events = new JwtBearerEvents();
options.Configuration = new OpenIdConnectConfiguration();
options.Events.OnTokenValidated = async context =>
{
IEnumerable<Claim> userClaims = await ReadClaimsFromCacheAsync(context);
if (ThereIsNoCache(userClaims))
{
userClaims = await GetUserInfoAsync(context, authority);
await StoreInCacheAsync(userClaims, context);
}
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(userClaims, "jwt", JwtClaimTypes.Subject, "roles"));
};
options.Events.OnAuthenticationFailed = async context =>
{
await TestInvalid(context);
};
}); IDX10500: Signature validation failed. No security keys were provided to validate the signature.
public class TokenValidatorService : ITokenValidatorService
{
private readonly BearerTokensOptionsDto _configuration;
public TokenValidatorService(IOptionsSnapshot<SiteSettingsDto> configuration)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
_configuration = configuration.Value?.BearerTokens ?? throw new ArgumentNullException(nameof(configuration));
}
public async Task<bool> IsValidJwtAsync(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var claimsPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidIssuer = _configuration.Issuer, // site that makes the token
ValidateIssuer = true,
ValidAudience = _configuration.Audience, // site that consumes the token
ValidateAudience = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.Key)),
ValidateIssuerSigningKey = true, // verify signature to avoid tampering
ValidateLifetime = true, // validate the expiration
ClockSkew = TimeSpan.Zero // tolerance for the expiration date
}, out var securityToken);
//var (success, _, _) = await IsValidClaimsPrincipalAsync(claimsPrincipal, securityToken);
//return success;
return true;
}
catch
{
return false;
}
}
} dotnet user-jwts create --scope "greetings_api" --role "admin"
if (!(context.SecurityToken is JwtSecurityToken accessToken) ||
string.IsNullOrWhiteSpace(accessToken.RawData) ||
!await _tokenStoreService.IsValidTokenAsync(accessToken.RawData, userId))
{
context.Fail("This token is not in our database.");
return;
} if (!(context.SecurityToken is JsonWebToken accessToken) ||
string.IsNullOrWhiteSpace(accessToken.UnsafeToString()) ||
!await tokenStoreService.IsValidTokenAsync(accessToken.UnsafeToString(), userId))
{
context.Fail("This token is not in our database.");
return;
}