Barış Kısır

Senior Software Developer

Navigation
 » Home
 » RSS

Implement OAuth 2.0 Authorization in Web API

26 May 2018 » csharp, security, sql

What is OAuth?

An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications.


NuGet Packages

Microsoft.Owin.Security.OAuth

Microsoft.AspNet.WebApi.Cors

Microsoft.AspNet.WebApi.Owin

Microsoft.AspNet.WebApi.WebHost

BCrypt-Official (Used for hashing passwords)


Adding OAuth Provider

public class OAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }
        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        if (context.ClientId == null)
        {
            context.Validated();
        }
        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        Uri expectedRootUri = new Uri(context.Request.Uri, "/");
        if (expectedRootUri.AbsoluteUri == context.RedirectUri)
        {
            context.Validated();
        }
        return Task.FromResult<object>(null);
    }

    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        return Task.Factory.StartNew(() =>
        {
            // context object contains user's crendentials when user is requesting token
            // Make user validation here
            var user = UserUtil.Login(context.UserName, context.Password);
            if (user != null)
            {
                var identity = new ClaimsIdentity(context.Options.AuthenticationType);
                identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
                identity.AddClaim(new Claim(ClaimTypes.Role, user.Role));

                // Custom claims can be set from here
                // identity.AddClaim(new Claim(ClaimTypes.Country, user.Country));
                // identity.AddClaim(new Claim(ClaimTypes.Gender, user.Gender));
                //

                context.Validated(identity);
            }
            else
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }
        });
    }
}


Enabling Oauth in Startup

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        HttpConfiguration httpConfiguration = new HttpConfiguration();
        ConfigureOAuth(appBuilder);
        WebApiConfig.Register(httpConfiguration);
        appBuilder.UseWebApi(httpConfiguration);
    }

    private void ConfigureOAuth(IAppBuilder appBuilder)
    {
        OAuthAuthorizationServerOptions oAuthAuthorizationServerOptions = new OAuthAuthorizationServerOptions()
        {
            // Set url for token requests
            TokenEndpointPath = new PathString("/token"),
            // Set token's lifetime
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(7),
            // HTTPS is highly recommended.
            AllowInsecureHttp = true,
            Provider = new OAuthProvider()
        };
        appBuilder.UseOAuthAuthorizationServer(oAuthAuthorizationServerOptions);
        appBuilder.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
    }
}


Register

We have register method below with anonymous access.

[Route("api/Register")]
[HttpPost]
[AllowAnonymous]
public bool Register(User user)
{
    using (var db = new NotesModel())
    {
        var result = UserUtil.Register(user.Username, user.Password);
        return result;
    }
}

register

register-db


How to get token

With registered credentials we can send post request to “/token” path.

token

We have 7 day valid token which has “Name” and “Role” claims encrypted inside it.


Adding token to request

In request’s header we should add “Authorization” key with “bearer token” value

add-note

add-note-2

get-notes

[Route("api/AddNote")]
[HttpPost]
[Authorize]
public bool AddNote([FromBody]string pNote)
{
    // Get user's claim informations according to token
    var username = User?.Identity?.Name;


Role based Authorization

We can restrict authorization with roles. In this example, only admin role can call the method below.

[Route("api/GetAllNotes")]
[HttpGet]
[Authorize(Roles = "Admin")]
public List<Note> GetAllNotes()
{
    using (var db = new NotesModel())
    {
        var noteList = db.Note.ToList();
        return noteList;
    }
}


Hashing password and verify

public class CryptoUtil
{
    public static string HashPassword(string password)
    {
        var salt = BCrypt.Net.BCrypt.GenerateSalt();
        var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, salt);
        return hashedPassword;
    }

    public static bool VerifyPassword(string password, string hashedPassword)
    {
        var isVerified = BCrypt.Net.BCrypt.Verify(password, hashedPassword);
        return isVerified;
    }
}


You can download source code, db script and postman requests from here –> Download