El artículo describe un método para conectar la autenticación multifactor para un sitio que se ejecuta en la plataforma principal .net utilizando mecanismos de autorización integrados.
Algunas palabras por las cuales se necesita autenticación multifactor:
- La seguridad
- Seguridad de nuevo
- Conveniencia
Sí, el último punto no es un error. El segundo y / o tercer factor de autenticación no es solo una adición a la contraseña tradicional, sino también un reemplazo completo. En lugar de sms con el código que necesita ser interrumpido en el sitio, los métodos modernos son PUSH en el messenger con un botón para confirmar la acción o la autenticación biométrica usando una huella digital en el teléfono o computadora portátil.
Principio de funcionamiento
- El sitio solicita y verifica el nombre de usuario / contraseña del usuario
- Si se especifican correctamente, envía al usuario a la página de autenticación a través del segundo factor
- Después de una verificación exitosa, el usuario regresa al sitio con un token de acceso e inicia sesión, es decir, recibe permiso para ver la parte cerrada del sitio.
Token de acceso
— 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("/");
}
Un borrador de trabajo con el código del artículo está disponible en GitHub . Para una comprensión más profunda de cómo funciona la autenticación basada en ClaimsIdentity en ASP.NET Core, consulte este excelente artículo .