Skip to content

Instantly share code, notes, and snippets.

@smaglio81
Created April 21, 2018 17:05
Show Gist options
  • Save smaglio81/41113c22a4aeb669aa753969dac4a796 to your computer and use it in GitHub Desktop.
Save smaglio81/41113c22a4aeb669aa753969dac4a796 to your computer and use it in GitHub Desktop.
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Principal;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
#if MVCEXTENSIONSNAME
namespace Ucsb.Sa.Enterprise.MvcExtensions
#else
namespace Ucsb.Sa.Enterprise.AspNet.WebApi
#endif
{
/// <summary>
/// http://aspnet.codeplex.com/sourcecontrol/latest#Samples/WebApi/BasicAuthentication/BasicAuthentication/Filters/BasicAuthenticationAttribute.cs
/// </summary>
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public virtual string Realm { get; set; }
public virtual async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
if (ValidateRequestInput(context, out var request, out var authInfo)) return;
string userName = authInfo.Username;
string password = authInfo.Password;
IPrincipal principal = await AuthenticateAsync(userName, password, context, cancellationToken);
if (principal == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
}
else
{
// Authentication was attempted and succeeded. Set Principal to the authenticated user.
context.Principal = principal;
}
}
protected virtual bool ValidateRequestInput(
HttpAuthenticationContext context,
out HttpRequestMessage request,
out BasicAuthenticationInfo authInfo)
{
request = context.Request;
authInfo = null;
AuthenticationHeaderValue authorization = request.Headers.Authorization;
if (authorization == null)
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return true;
}
if (authorization.Scheme != "Basic")
{
// No authentication was attempted (for this authentication method).
// Do not set either Principal (which would indicate success) or ErrorResult (indicating an error).
return true;
}
if (String.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return true;
}
authInfo = ExtractUsernameAndPassword(authorization.Parameter);
if (authInfo == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
return true;
}
return false;
}
protected abstract Task<IPrincipal> AuthenticateAsync(
string userName,
string password,
HttpAuthenticationContext context,
CancellationToken cancellationToken
);
protected static BasicAuthenticationInfo ExtractUsernameAndPassword(string authorizationParameter)
{
byte[] credentialBytes;
try
{
credentialBytes = Convert.FromBase64String(authorizationParameter);
}
catch (FormatException)
{
return null;
}
// The currently approved HTTP 1.1 specification says characters here are ISO-8859-1.
// However, the current draft updated specification for HTTP 1.1 indicates this encoding is infrequently
// used in practice and defines behavior only for ASCII.
Encoding encoding = Encoding.ASCII;
// Make a writable copy of the encoding to enable setting a decoder fallback.
encoding = (Encoding)encoding.Clone();
// Fail on invalid bytes rather than silently replacing and continuing.
encoding.DecoderFallback = DecoderFallback.ExceptionFallback;
string decodedCredentials;
try
{
decodedCredentials = encoding.GetString(credentialBytes);
}
catch (DecoderFallbackException)
{
return null;
}
if (String.IsNullOrEmpty(decodedCredentials))
{
return null;
}
int colonIndex = decodedCredentials.IndexOf(':');
if (colonIndex == -1)
{
return null;
}
string username = decodedCredentials.Substring(0, colonIndex);
string password = decodedCredentials.Substring(colonIndex + 1);
var authInfo = new BasicAuthenticationInfo()
{
Username = username,
Password = password
};
return authInfo;
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
Challenge(context);
return Task.FromResult(0);
}
protected virtual void Challenge(HttpAuthenticationChallengeContext context)
{
string parameter;
if (String.IsNullOrEmpty(Realm))
{
parameter = null;
}
else
{
// A correct implementation should verify that Realm does not contain a quote character unless properly
// escaped (precededed by a backslash that is not itself escaped).
parameter = "realm=\"" + Realm + "\"";
}
context.ChallengeWith("Basic", parameter);
}
public virtual bool AllowMultiple
{
get { return false; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment