Multifaktorauthentifizierung verbinden Multifaktor in .NET Core

Der Artikel beschreibt eine Methode zum Verbinden der Multifaktorauthentifizierung für eine Site, die auf der .net-Kernplattform ausgeführt wird, mithilfe integrierter Autorisierungsmechanismen.


Ein paar Worte, warum überhaupt eine Multifaktorauthentifizierung erforderlich ist:


  1. Sicherheit
  2. Wieder Sicherheit
  3. Bequemlichkeit

Ja, der letzte Punkt ist kein Fehler. Der zweite und / oder dritte Authentifizierungsfaktor ist nicht nur eine Ergänzung zum herkömmlichen Passwort, sondern auch ein vollständiger Ersatz. Anstelle von SMS mit dem Code, der auf der Site unterbrochen werden muss, werden moderne Methoden im Messenger mit einer Taste gedrückt, um die Aktion oder die biometrische Authentifizierung mithilfe eines Fingerabdrucks auf dem Telefon oder Laptop zu bestätigen.


Arbeitsprinzip


  1. Die Site fordert den Benutzernamen / das Passwort des Benutzers an und überprüft ihn
  2. Wenn sie korrekt angegeben sind, wird der Benutzer über den zweiten Faktor zur Authentifizierungsseite gesendet
  3. Nach erfolgreicher Überprüfung kehrt der Benutzer mit einem Zugriffstoken zur Site zurück und meldet sich an, erhält also die Berechtigung zum Anzeigen des geschlossenen Teils der Site.

Zugangstoken


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

Ein Arbeitsentwurf mit dem Code aus dem Artikel ist auf GitHub verfügbar . Weitere Informationen zur Funktionsweise der ClaimsIdentity-basierten Authentifizierung in ASP.NET Core finden Sie in diesem großartigen Artikel .

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


All Articles