Connexion de Multifactor Authentification Multifactor dans .NET Core

L'article décrit une méthode de connexion de l'authentification multifactorielle pour un site s'exécutant sur la plateforme principale .net à l'aide de mécanismes d'autorisation intégrés.


Quelques mots pourquoi l'authentification multifactorielle est nécessaire:


  1. sécurité
  2. La sécurité à nouveau
  3. Commodité

Oui, le dernier point n'est pas une erreur. Le deuxième et / ou le troisième facteur d'authentification n'est pas seulement un ajout au mot de passe traditionnel, mais également un remplacement complet. Au lieu de sms avec le code qui doit être interrompu sur le site, les méthodes modernes sont PUSH dans le messager avec un bouton pour confirmer l'action ou l'authentification biométrique en utilisant une empreinte digitale sur le téléphone ou l'ordinateur portable.


Principe d'opération


  1. Le site demande et vérifie le nom d'utilisateur / mot de passe de l'utilisateur
  2. S'ils sont spécifiés correctement, envoie l'utilisateur à la page d'authentification via le deuxième facteur
  3. Après une vérification réussie, l'utilisateur revient sur le site avec un jeton d'accès et se connecte, c'est-à-dire qu'il reçoit l'autorisation d'afficher la partie fermée du site.

Jeton d'accès


— 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("/");
}

Un brouillon de travail avec le code de l'article est disponible sur GitHub . Pour une compréhension plus approfondie du fonctionnement de l'authentification basée sur ClaimsIdentity dans ASP.NET Core, consultez cet excellent article .

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


All Articles