Last active
April 23, 2016 12:01
-
-
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)
This file contains hidden or 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
| //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; | |
| } | |
| } |
This file contains hidden or 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
| 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