Conectando multifator Multifator de autenticação no .NET Core

O artigo descreve um método para conectar a autenticação multifator para um site em execução na plataforma principal .net usando mecanismos de autorização internos.


Algumas palavras por que a autenticação multifator é necessária:


  1. Segurança
  2. Segurança novamente
  3. Conveniência

Sim, o último ponto não é um erro. O segundo e / ou terceiro fator de autenticação não é apenas uma adição à senha tradicional, mas também uma substituição completa. Em vez de sms com o código que precisa ser interrompido no site, os métodos modernos são PUSH no messenger com um botão para confirmar a ação ou autenticação biométrica usando uma impressão digital no telefone ou laptop.


Princípio da Operação


  1. O site solicita e verifica o nome de usuário / senha do usuário
  2. Se eles forem especificados corretamente, envia o usuário para a página de autenticação através do segundo fator
  3. Após a verificação bem-sucedida, o usuário retorna ao site com o token de acesso e efetua login, ou seja, recebe permissão para visualizar a parte fechada do site.

Token de acesso


— JWT (JSON Web Token). . , , JSON . : , . base64-url. ,


{
  "typ": "JWT",     // : JWT
  "alg": "HS256"    // : HMAC   SHA-256
}

, , , , . : , claims . , iss, aud, sub .


{
  "iss": "https://access.multifactor.ru",   // 
  "aud": "https://example.com",             // 
  "sub": "user@example.com",                // 
  "jti": "RxMEyo9",                         //id 
  "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. :


  1. .


appsettings.json API multifactor.ru


"Multifactor": {
    "ApiKey": "",
    "ApiSecret": "",
    "CallbackUrl": "https://localhost:44300/account/mfa"
  }

  • ApiKey ApiSecret
  • CallbackUrl —

API


API multifactor.ru


/// <summary>
/// Multifactor Client
/// </summary>
public class MultifactorService
{
    //    API
    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,    //login 
            Callback = new
            {
                Action = _callbackUrl,  // 
                Target = "_self"
            },
            Claims = claims             // 
        });

        var payLoad = Encoding.UTF8.GetBytes(request);

        //basic authorization
        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


//load Multifactor settings
var multifactorSection = Configuration.GetSection("Multifactor");
var apiKey = multifactorSection["ApiKey"];
var apiSecret = multifactorSection["ApiSecret"];
var callbackUrl = multifactorSection["CallbackUrl"];

//register Multifactor service
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)), //signature validation key
            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();
    });


//redirect to /account/login when unauthorized
    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)
    {
        // identity provider      
        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("/");
}

Um rascunho de trabalho com o código do artigo está disponível no GitHub . Para uma compreensão mais profunda de como funciona a autenticação baseada em declarações de identidade no ASP.NET Core, confira este excelente artigo .

Source: https://habr.com/ru/post/undefined/


All Articles