рд▓реЗрдЦ рдирд┐рд░реНрдорд┐рддред рдкреНрд░рд╛рдзрд┐рдХрд░рдг рдордВрдЪ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП .net рдХреЛрд░ рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рдкрд░ рдЪрд▓рдиреЗ рд╡рд╛рд▓реА рд╕рд╛рдЗрдЯ рдХреЗ рд▓рд┐рдП рдорд▓реНрдЯреАрдлрд╝реЙрд░реНрдорд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреА рдПрдХ рд╡рд┐рдзрд┐ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддрд╛ рд╣реИред
рдорд▓реНрдЯреАрдПрдХреНрдЯрд░ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреЗ рд▓рд┐рдП рдХреБрдЫ рд╢рдмреНрджреЛрдВ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреНрдпреЛрдВ рд╣реИ:
- рд╕реБрд░рдХреНрд╖рд╛
 - рдлрд┐рд░ рд╕реЗ рд╕реБрд░рдХреНрд╖рд╛
 - рд╕реБрд╡рд┐рдзрд╛
 
рд╣рд╛рдВ, рдЖрдЦрд┐рд░реА рдмрд┐рдВрджреБ рдЧрд▓рддреА рдирд╣реАрдВ рд╣реИред рджреВрд╕рд░рд╛ рдФрд░ / рдпрд╛ рддреАрд╕рд░рд╛ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХрд╛рд░рдХ рди рдХреЗрд╡рд▓ рдкрд╛рд░рдВрдкрд░рд┐рдХ рдкрд╛рд╕рд╡рд░реНрдб рдХреЗ рд▓рд┐рдП рдПрдХ рдЕрддрд┐рд░рд┐рдХреНрдд рд╣реИ, рдмрд▓реНрдХрд┐ рдПрдХ рдкреВрд░реНрдг рдкреНрд░рддрд┐рд╕реНрдерд╛рдкрди рднреА рд╣реИред рд╕рд╛рдЗрдЯ рдкрд░ рдмрд╛рдзрд┐рдд рдХрд┐рдП рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рдХреЛрдб рдХреЗ рд╕рд╛рде рдПрд╕рдПрдордПрд╕ рдХреЗ рдмрдЬрд╛рдп, рдЖрдзреБрдирд┐рдХ рддрд░реАрдХреЗ рдлреЛрди рдпрд╛ рд▓реИрдкрдЯреЙрдк рдкрд░ рдПрдХ рдлрд┐рдВрдЧрд░рдкреНрд░рд┐рдВрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдХрд╛рд░реНрд░рд╡рд╛рдИ рдпрд╛ рдмреЙрдпреЛрдореАрдЯреНрд░рд┐рдХ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреА рдкреБрд╖реНрдЯрд┐ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдЯрди рдХреЗ рд╕рд╛рде рдореИрд╕реЗрдВрдЬрд░ рдореЗрдВ PUSH рд╣реИрдВред
рд╕рдВрдЪрд╛рд▓рди рдХрд╛ рд╕рд┐рджреНрдзрд╛рдВрдд
- рд╕рд╛рдЗрдЯ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо / рдкрд╛рд╕рд╡рд░реНрдб рдХрд╛ рдЕрдиреБрд░реЛрдз рдФрд░ рд╕рддреНрдпрд╛рдкрди рдХрд░рддреА рд╣реИ
 - рдпрджрд┐ рд╡реЗ рд╕рд╣реА рддрд░реАрдХреЗ рд╕реЗ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рд╣реИрдВ, рддреЛ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдХреЛ рджреВрд╕рд░реЗ рдХрд╛рд░рдХ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдкреГрд╖реНрда рдкрд░ рднреЗрдЬрддрд╛ рд╣реИ
 - рдПрдХ рд╕рдлрд▓ рд╕рддреНрдпрд╛рдкрди рдХреЗ рдмрд╛рдж, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдПрдХ рдПрдХреНрд╕реЗрд╕ рдЯреЛрдХрди рдХреЗ рд╕рд╛рде рд╕рд╛рдЗрдЯ рдкрд░ рд▓реМрдЯрддрд╛ рд╣реИ рдФрд░ рд▓реЙрдЧ рдЗрди рдХрд░рддрд╛ рд╣реИ, рдЕрд░реНрдерд╛рдд рд╕рд╛рдЗрдЯ рдХреЗ рдмрдВрдж рд╣рд┐рд╕реНрд╕реЗ рдХреЛ рджреЗрдЦрдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдкреНрд░рд╛рдкреНрдд рдХрд░рддрд╛ рд╣реИред
 
рдПрдХреНрд╕реЗрд╕ рдЯреЛрдХрди
тАФ JWT (JSON Web Token). . , , JSON . : , . base64-url. ,
{
  "typ": "JWT",     
  "alg": "HS256"    
}
, , , , . : , claims . , iss, aud, sub .
{
  "iss": "https://access.multifactor.ru",   
  "aud": "https://example.com",             
  "sub": "user@example.com",                
  "jti": "RxMEyo9",                         
  "iat": 1571684399,                        
  "exp": 1571684699,                        
  "returnUrl": "/",                         
  "rememberMe": "False",                    
  "createdAt": "10/21/19 6:59:55 PM"        
}
JWT тАФ , , HMAC-SHA256(message, secret), :
- message тАФ , base64-url ;
 - secret тАФ , , .
 
JWT , , , , e-mail, .
JWT , , . , .
, ASP.NET Core Web Application. :
- .
 
appsettings.json API multifactor.ru
"Multifactor": {
    "ApiKey": "",
    "ApiSecret": "",
    "CallbackUrl": "https://localhost:44300/account/mfa"
  }
- ApiKey ApiSecret
 - CallbackUrl тАФ
 
API
API multifactor.ru
public class MultifactorService
{
    
    private string _apiKey;
    private string _apiSecret;
    private string _callbackUrl;
    private string _apiHost = "https://api.multifactor.ru";
    public MultifactorService(string apiKey, string apiSecret, string callbackUrl)
    {
        _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
        _apiSecret = apiSecret ?? throw new ArgumentNullException(nameof(apiSecret));
        _callbackUrl = callbackUrl ?? throw new ArgumentNullException(nameof(callbackUrl));
    }
    public async Task<string> GetAccessPage(string identityName, IDictionary<string, string> claims = null)
    {
        if (string.IsNullOrEmpty(identityName)) throw new ArgumentNullException(nameof(identityName));
        var request = JsonConvert.SerializeObject(new
        {
            Identity = identityName,    
            Callback = new
            {
                Action = _callbackUrl,  
                Target = "_self"
            },
            Claims = claims             
        });
        var payLoad = Encoding.UTF8.GetBytes(request);
        
        var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(_apiKey + ":" + _apiSecret));
        using var client = new WebClient();
        client.Headers.Add("Authorization", "Basic " + authHeader);
        client.Headers.Add("Content-Type", "application/json");
        var responseData = await client.UploadDataTaskAsync(_apiHost + "/access/requests", "POST", payLoad);
        var responseJson = Encoding.ASCII.GetString(responseData);
        var response = JsonConvert.DeserializeObject<MultifactorResponse<MultifactorAccessPage>>(responseJson);
        return response.Model.Url; 
    }
    internal class MultifactorResponse<T>
    {
        public bool Success { get; set; }
        public T Model { get; set; }
    }
    internal class MultifactorAccessPage
    {
        public string Url { get; set; }
    }
}
тАФ API . , appsettings.json. GetAccessPage , API .
Startup.cs, ConfigureServices API
var multifactorSection = Configuration.GetSection("Multifactor");
var apiKey = multifactorSection["ApiKey"];
var apiSecret = multifactorSection["ApiSecret"];
var callbackUrl = multifactorSection["CallbackUrl"];
var multifactorService = new MultifactorService(apiKey, apiSecret, callbackUrl);
services.AddSingleton(multifactorService);
.net core . , . JWT тАФ Bearer . , JWT HTTP "Authorization: Bearer", .NET Core , , .
.net JWT Bearer
services
    .AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.RequireHttpsMetadata = true;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(apiSecret)), 
            ValidateIssuer = true,
            ValidIssuer = "https://access.multifactor.ru",
            ValidateAudience = false,
            NameClaimType = ClaimTypes.NameIdentifier
        };
    });
:
- ValidateIssuerSigningKey тАФ
 - IssuerSigningKey ApiSecret API
 - ValidateIssuer тАФ
 - NameClaimType
 
Middleware
Configure , JWT Bearer
app.Use(async (context, next) =>
    {
        var token = context.Request.Cookies["jwt"];
        if (!string.IsNullOrEmpty(token))
        {
            context.Request.Headers.Add("Authorization", "Bearer " + token);
        }
        await next();
    });
    app.UseStatusCodePages(async context => {
        var response = context.HttpContext.Response;
        if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
        {
            response.Redirect("/account/login");
        }
    });
AccountController
, . , AccountController, .
API multifactor.ru
private MultifactorService _multifactorService;
[HttpPost("/account/login")]
public async Task<IActionResult> Login([Required]string login, [Required]string password)
{
    if (ModelState.IsValid)
    {
        
        var isValidUser = _identityService.ValidateUser(login, password, out string role);
        if (isValidUser)
        {
            var claims = new Dictionary<string, string> 
            {
                { "Role", role }
            };
            var url = await _multifactorService.GetAccessPage(login, claims);
            return RedirectPermanent(url);
        }
    }
    return View();
}
тАФ
[HttpPost("/account/mfa")]
public IActionResult MultifactorCallback(string accessToken)
{
    
    Response.Cookies.Append("jwt", accessToken);
    return LocalRedirect("/");
}
, , Logout,
[HttpGet("/account/logout")]
public IActionResult Logout()
{
    Response.Cookies.Delete("jwt");
    return Redirect("/");
}
рд▓реЗрдЦ рд╕реЗ рдХреЛрдб рдХреЗ рд╕рд╛рде рдПрдХ рдХрд╛рдо рдХрд╛ рдорд╕реМрджрд╛ GitHub рдкрд░ рдЙрдкрд▓рдмреНрдз рд╣реИ ред ASP.NET Core рдореЗрдВ ClaimsIdentity- рдЖрдзрд╛рд░рд┐рдд рдкреНрд░рдорд╛рдгреАрдХрд░рдг рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рдЗрд╕рдХреА рдЧрд╣рди рд╕рдордЭ рдХреЗ рд▓рд┐рдП, рдЗрд╕ рдорд╣рд╛рди рд▓реЗрдЦ рдХреЛ рджреЗрдЦреЗрдВ ред