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.