Integrating .NET Core
The article considers an example of multifactor authentication for the sites, based on a platform .NET Core.
As a basis, it is used templated project ASP.NET Core Web Application.
Operation Principles
- Website validates the user login and password
- If data is valid, it requests API of multifactor for getting an access page address and forwards the user to this page
- Gets an access token from Multifactor and saves it in the user's cookies
- Upon each request, it takes access token from cookies and adds in header Authorization: Bearer
- Validates token and permits access
Configuration file
Add to the file appsettings.json the section with parameters for connection to Multifactor API
"Multifactor": {
"ApiKey": "",
"ApiSecret": "",
"CallbackUrl": "https://localhost:44300/account/mfa"
},
- Parameters ApiKey and ApiSecret are accessible in Administrative panel
- CallbackUrl — this is the redirect address of user to your site
API client
Add to project the service for interaction with Multifactor API
/// <summary>
/// Multifactor Client
/// </summary>
public class MultifactorService
{
//parameters for connection to 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, //user login
Callback = new
{
Action = _callbackUrl, //return address
Target = "_self"
},
Claims = claims //set of 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; //access page address
}
internal class MultifactorResponse<T>
{
public bool Success { get; set; }
public T Model { get; set; }
}
internal class MultifactorAccessPage
{
public string Url { get; set; }
}
}
Service — client for Multifactor API. The parameters for connection are transmitted in the class constructor, which are collected from configuration file appsettings.json. In method GetAccessPage you form a request to get the address of access validation page, then you request API and receive a response.
Implementation of interactions and services setup
Edit the file Startup.cs, add to the method ConfigureServices the code to load settings from the configuration file and to register a client for 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);
In .NET Core, there are considered several schemes of authentication. In a single application, it is possible to use one or simultaneously several, in dependence on scripts. The most convenient scheme for using authentication on the base of JWT token — is Bearer authentication. In accordance with this scheme, JWT token is transmitted in HTTP header "Authorization: Bearer", а .NET Core automatically checks signature, validity period, other attributes of token and authorizes the user.
The next code transfers authorization scheme .net to 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
};
});
You shall pay attention for the following:
- ValidateIssuerSigningKey — to validate token signing key
- IssuerSigningKey matches with ApiSecret from settings of access to API
- ValidateIssuer — it is necessary to validate token issuer
- NameClaimType points which claim shall be used to get the user indentificator
Middleware
Add to method Configure code, which gets access token from cookies and addresses it in JWT Bearer header
app.Use(async (context, next) =>
{
var token = context.Request.Cookies["jwt"];
if (!string.IsNullOrEmpty(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
await next();
});
Declare address of login form
//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
All the arrangements are completed, it remains to finalize the login form script and get the access token. It's as if your project has class AccountController, which requests the user login and password.
Add to it the service for operation with Multifactor API
private MultifactorService _multifactorService;
Make a request for multifactor authentication after validation of login and password
[HttpPost("/account/login")]
public async Task<IActionResult> Login([Required]string login, [Required]string password)
{
if (ModelState.IsValid)
{
//your identity provider to validate the user login and password
var isValidUser = _identityService.ValidateUser(login, password, out string role);
if (isValidUser)
{
var claims = new Dictionary<string, string> //you may add in token the role and any other attributes of the user, so that do not request them from database
{
{ "Role", role }
};
var url = await _multifactorService.GetAccessPage(login, claims);
return RedirectPermanent(url);
}
}
return View();
}
The last issue — it's the user return address after successful authentication by means of access token
[HttpPost("/account/mfa")]
public IActionResult MultifactorCallback(string accessToken)
{
//save token in cookies and forward user to authorized area
Response.Cookies.Append("jwt", accessToken);
return LocalRedirect("/");
}
In order to logout the user, use Logout method, which delete cookies with token
[HttpGet("/account/logout")]
public IActionResult Logout()
{
Response.Cookies.Delete("jwt");
return Redirect("/");
}
Please see the following: