Created
April 21, 2018 17:05
-
-
Save smaglio81/41113c22a4aeb669aa753969dac4a796 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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