Skip to content

Instantly share code, notes, and snippets.

@naiduv
Last active April 23, 2016 12:01
Show Gist options
  • Select an option

  • Save naiduv/c7803fe5e8bcb80f979cb83cfa536774 to your computer and use it in GitHub Desktop.

Select an option

Save naiduv/c7803fe5e8bcb80f979cb83cfa536774 to your computer and use it in GitHub Desktop.
Code for setting up basic authentication for an asp.net webapi 2 webservice (like azure mobile app or mobile service)
//You should be able to simply copy and paste this code once you add this cs file and add the [Authorize] attribute to your controller action
//When you call the action that needs auth from the broser you would need to specify the username password pair ("user"/"passw")
public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public string Realm { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
HttpRequestMessage request = context.Request;
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;
}
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;
}
if (String.IsNullOrEmpty(authorization.Parameter))
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
return;
}
Tuple<string, string> userNameAndPasword = ExtractUserNameAndPassword(authorization.Parameter);
if (userNameAndPasword == null)
{
// Authentication was attempted but failed. Set ErrorResult to indicate an error.
context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
return;
}
string userName = userNameAndPasword.Item1;
string password = userNameAndPasword.Item2;
IPrincipal principal = await AuthenticateAsync(userName, password, 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 abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,
CancellationToken cancellationToken);
private static Tuple<string, string> 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);
return new Tuple<string, string>(userName, password);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
Challenge(context);
return Task.FromResult(0);
}
private 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; }
}
}
public static class HttpAuthenticationChallengeContextExtensions
{
public static void ChallengeWith(this HttpAuthenticationChallengeContext context, string scheme)
{
ChallengeWith(context, new AuthenticationHeaderValue(scheme));
}
public static void ChallengeWith(this HttpAuthenticationChallengeContext context, string scheme, string parameter)
{
ChallengeWith(context, new AuthenticationHeaderValue(scheme, parameter));
}
public static void ChallengeWith(this HttpAuthenticationChallengeContext context, AuthenticationHeaderValue challenge)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
}
}
public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
public AuthenticationHeaderValue Challenge { get; private set; }
public IHttpActionResult InnerHttpResult { get; private set; }
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
Challenge = challenge;
InnerHttpResult = innerResult;
}
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await InnerHttpResult.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Only add one challenge per authentication scheme.
if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(Challenge);
}
}
return response;
}
}
public class IdentityBasicAuthenticationAttribute : BasicAuthenticationAttribute
{
protected override async Task<IPrincipal> AuthenticateAsync(string userName, string password, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (userName != "user" || password != "passw")
{
// No user with userName/password exists.
return null;
}
// Create a ClaimsIdentity with all the claims for this user.
Claim nameClaim = new Claim(ClaimTypes.Name, userName);
List<Claim> claims = new List<Claim> { nameClaim };
// important to set the identity this way, otherwise IsAuthenticated will be false
// see: http://leastprivilege.com/2012/09/24/claimsidentity-isauthenticated-and-authenticationtype-in-net-4-5/
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationTypes.Basic);
var principal = new ClaimsPrincipal(identity);
return principal;
}
}
public class AuthenticationFailureResult : IHttpActionResult
{
public string ReasonPhrase { get; private set; }
public HttpRequestMessage Request { get; private set; }
public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
{
ReasonPhrase = reasonPhrase;
Request = request;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(execute());
}
private HttpResponseMessage execute()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
response.RequestMessage = Request;
response.ReasonPhrase = ReasonPhrase;
return response;
}
}
public class yourApiController: ApiController
{
//Add this to any controller or action that needs authorization
[IdentityBasicAuthentication]
[Authorize]
public action(){}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment