Skip to content

Instantly share code, notes, and snippets.

@GeorgDangl
Last active June 5, 2019 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save GeorgDangl/70dca6188e351496145e83f2f76b2385 to your computer and use it in GitHub Desktop.
Save GeorgDangl/70dca6188e351496145e83f2f76b2385 to your computer and use it in GitHub Desktop.
Asp.Net Core middleware for Asp.Net Identity projects to support Http Basic authentication, see https://blog.dangl.me/archive/http-basic-authentication-in-aspnet-core-projects
using System;
namespace Project.Middleware
{
public class BasicAuthenticationHeaderValue
{
public BasicAuthenticationHeaderValue(string authenticationHeaderValue)
{
if (!string.IsNullOrWhiteSpace(authenticationHeaderValue))
{
_authenticationHeaderValue = authenticationHeaderValue;
if (TryDecodeHeaderValue())
{
ReadAuthenticationHeaderValue();
}
}
}
private readonly string _authenticationHeaderValue;
private string[] _splitDecodedCredentials;
public bool IsValidBasicAuthenticationHeaderValue { get; private set; }
public string UserIdentifier { get; private set; }
public string UserPassword { get; private set; }
private bool TryDecodeHeaderValue()
{
const int headerSchemeLength = 6; // "Basic ".Length;
if (_authenticationHeaderValue.Length <= headerSchemeLength)
{
return false;
}
var encodedCredentials = _authenticationHeaderValue.Substring(headerSchemeLength);
try
{
var decodedCredentials = Convert.FromBase64String(encodedCredentials);
var plainCredentials = System.Text.Encoding.ASCII.GetString(decodedCredentials);
// The username may not include colon per the RFC:
// https://tools.ietf.org/html/rfc2617#section-2
var colonIndex = plainCredentials.IndexOf(':');
if (colonIndex < 0)
{
return false;
}
_splitDecodedCredentials = new string[]
{
plainCredentials.Substring(0, colonIndex),
plainCredentials.Substring(colonIndex + 1)
};
return true;
}
catch (FormatException)
{
return false;
}
}
private void ReadAuthenticationHeaderValue()
{
IsValidBasicAuthenticationHeaderValue = _splitDecodedCredentials.Length == 2
&& !string.IsNullOrWhiteSpace(_splitDecodedCredentials[0])
&& !string.IsNullOrWhiteSpace(_splitDecodedCredentials[1]);
if (IsValidBasicAuthenticationHeaderValue)
{
UserIdentifier = _splitDecodedCredentials[0];
UserPassword = _splitDecodedCredentials[1];
}
}
}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace Project.Middleware
{
/// <summary>
/// Accepts either username or email as user identifier for sign in with Http Basic authentication
/// </summary>
public class BasicAuthenticationMiddleware
{
public BasicAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
private readonly RequestDelegate _next;
public async Task Invoke(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
var basicAuthenticationHeader = GetBasicAuthenticationHeaderValue(context);
if (basicAuthenticationHeader.IsValidBasicAuthenticationHeaderValue)
{
var authenticationManager = new BasicAuthenticationSignInManager(context, basicAuthenticationHeader);
await authenticationManager.TrySignInUser();
}
}
await _next.Invoke(context);
}
private BasicAuthenticationHeaderValue GetBasicAuthenticationHeaderValue(HttpContext context)
{
var basicAuthenticationHeader = context.Request.Headers["Authorization"]
.FirstOrDefault(header => header.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
var decodedHeader = new BasicAuthenticationHeaderValue(basicAuthenticationHeader);
return decodedHeader;
}
}
}
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
namespace Project.Middleware
{
public class BasicAuthenticationSignInManager
{
public BasicAuthenticationSignInManager(HttpContext context, BasicAuthenticationHeaderValue authenticationHeaderValue)
{
_context = context;
_authenticationHeaderValue = authenticationHeaderValue;
_userManager = _context.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
_signInManager = _context.RequestServices.GetRequiredService<SignInManager<IdentityUser>>();
}
private readonly HttpContext _context;
private readonly BasicAuthenticationHeaderValue _authenticationHeaderValue;
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private IdentityUser _user;
public async Task TrySignInUser()
{
if (_authenticationHeaderValue.IsValidBasicAuthenticationHeaderValue)
{
await GetUserByUsernameOrEmail();
if (_user != null)
{
await SignInUserIfPasswordIsCorrect();
}
}
}
private async Task GetUserByUsernameOrEmail()
{
_user = await _userManager.FindByEmailAsync(_authenticationHeaderValue.UserIdentifier)
?? await _userManager.FindByNameAsync(_authenticationHeaderValue.UserIdentifier);
}
private async Task SignInUserIfPasswordIsCorrect()
{
if (await _userManager.CheckPasswordAsync(_user, _authenticationHeaderValue.UserPassword))
{
_context.User = await _signInManager.CreateUserPrincipalAsync(_user);
}
}
}
}
using Microsoft.AspNetCore.Builder;
namespace Project.Middleware
{
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseBasicAuthentication(this IApplicationBuilder builder)
{
return builder.UseMiddleware<BasicAuthenticationMiddleware>();
}
}
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseIdentity();
// Add middleware after Identity but before Mvc
app.UseBasicAuthentication();
app.UseMvc();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment