Working further on my new Quid WebApi, then next step is to add Authentication. I'm going to use basic authentication for this. Here is an overview of the steps I followed:
- Custom AuthenticationHandler
- Startup code
- [Authorize] attribute
- Get current user
Custom AuthenticationHandler
The bulk of the implementation is found in a custom object inheriting from AuthenticationHandler. For this I created a class called BasicAuthenticationHandler. Here is that class in its entirety:
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
public const string AuthHeaderName = "Authorization";
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey(AuthHeaderName))
{
return AuthenticateResult.Fail("Authorization header was not found.");
}
// Grab the params and decrypt
var authHeaderValue = AuthenticationHeaderValue.Parse(Request.Headers[AuthHeaderName]);
var bytes = Convert.FromBase64String(authHeaderValue.Parameter);
string[] credentials = Encoding.UTF8.GetString(bytes).Split(":");
string username = null;
// TODO: Add some custom validation code here:
username = credentials[0]; // Just set it to the first string
if (!string.IsNullOrEmpty(username))
{
var claims = new[] { new Claim(ClaimTypes.Name, username) };
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
return AuthenticateResult.Fail("Unauthorized");
}
}
The parameters coming in need to be UTF8 decrypted, which is done on lines 21-22. The credentials variables on line 23 looks like "username:password". Line 27 is where you would put some form of custom authentication. The rest of this class should be either self-explanatory, or "just works". The primary change you would need is line 27.
Startup code
The next set of code all appears in Startup.cs. The first is a one-liner in Create:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
...
}
The next set of code is added to ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
...
// Specify the Authentication for this app to use:
services.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions,
BasicAuthenticationHandler>("BasicAuthentication", null);
...
}
The "BasicAuthentication" pass to the constructor (line 5) is simply the custom class created above minus "Handle" on the end. Note this is also passed to AddScheme.
[Authorize] attribute
The last required step is to simply add the [Authorize] attribute to any Controller or Method that requires it. Here I place it on my QuidController:
[Authorize]
[ApiController]
[Route("[controller]")]
public class QuidController : ControllerBase
{
...
}
Get current user
If you have need of the current user, this can be obtained via the HttpContext:
[HttpGet]
[Route("AuthTest")]
public string AuthTest()
{
string username = HttpContext.User.Identity.Name;
return $"Welcome {username}";
}
Wrapping up...
That's all that's needed for basic authentication on the service side. At this point, ensure all calls include basic authentication and include username & pwd. This can be tested easily from PostMan.
In Part II, I show how to make this call from an Android app.