Skip to main content

Integration for .NET Core

The article discusses an example of connecting two-factor authentication for sites built on the .NET Core platform. The ASP.NET Core Web Application template project is used as a basis.

Principle of operation

  1. The site checks the user’s login and password
  2. If the data is specified correctly, requests API of the multifactor to obtain the address of the access page and sends the user to it
  3. Receives an access token from Multifactor and saves it in the user’s cookies
  4. For each request, takes the access token from the cookie and adds it to the Authorization header: Bearer
  5. Validates the token and allows access

Configuration file

Add a section to the appsettings.json file with parameters for connecting to the Multifactor API

"Multifactor": {
"ApiKey": "",
"ApiSecret": "",
"CallbackUrl": "https://localhost:44300/account/mfa"
},
  • ApiKey and ApiSecret parameters are available in personal account
  • CallbackUrl is the user’s return address to your site

Client for API

Add a service to the project to interact with Multifactor API

/// <summary>
/// Multifactor Client
/// </summary>
public class MultifactorService
{
//parameters for connecting to the API
private string _apiKey;
private string _apiSecret;
private string _callbackUrl;

private string _apiHost = "https://api.multifactor.kz";

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; }
}
}

The service is a client for the Multifactor API. Connection parameters are passed to the class constructor, which are taken from the appsettings.json configuration file. In the GetAccessPage method, a request is generated to obtain the address of the access verification page, then API is requested and the result is returned.

Injecting dependencies and configuring services

Edit the Startup.cs file, add code to the ConfigureServices method to load settings from the configuration file and register the 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);

.net core provides several authentication schemes. In a single application, you can use one or more at the same time, depending on the scenarios. The most convenient scheme for using JWT token-based authentication is Bearer authentication. In accordance with this scheme, the JWT token is transmitted in the HTTP header "Authorization: Bearer", and .NET Core automatically checks the signature, expiration date, and other attributes of the token and authorizes the user.

The following code switches the .net authorization scheme 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.kz",
ValidateAudience = false,
NameClaimType = ClaimTypes.NameIdentifier
};
});

Here you need to pay attention to the following points:

  • ValidateIssuerSigningKey - it is necessary to check the token signature
  • IssuerSigningKey matches ApiSecret from API access settings
  • ValidateIssuer - it is necessary to check the issuer of the token
  • NameClaimType indicates which application to take the user ID from

##Middleware

Add code to the Configure method that takes the access token from the cookie and puts it in the 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();
});

And enter the address of the 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 preparatory work is completed, all that remains is to complete the script for the login form and obtaining an access token. Let's assume that your project has a class AccountController that requests the user's login and password.

Add a service to it for working with Multifactor API

private MultifactorService _multifactorService;

And make a request for multi-factor authentication after checking your login and password

[HttpPost("/account/login")]
public async Task<IActionResult> Login([Required]string login, [Required]string password)
{
if (ModelState.IsValid)
{
//your identity provider for checking the user's login and password
var isValidUser = _identityService.ValidateUser(login, password, out string role);

if(isValidUser)
{
var claims = new Dictionary<string, string> //you can add the role and any other user attributes to the token so as not to request them from the database
{
{"Role", role}
};

var url = await _multifactorService.GetAccessPage(login, claims);
return RedirectPermanent(url);
}
}

return View();
}

The last point is the user's return address after successful authentication with the access token

[HttpPost("/account/mfa")]
public IActionResult MultifactorCallback(string accessToken)
{
//save the token in cookies and send the user to the authorized zone
Response.Cookies.Append("jwt", accessToken);
return LocalRedirect("/");
}

In order to log out the user, create the Logout method, which deletes cookies with a token

[HttpGet("/account/logout")]
public IActionResult Logout()
{
Response.Cookies.Delete("jwt");
return Redirect("/");
}

See also: