1
votes

I have followed this tutorial Create a RESTful API with authentication using Web API and Jwt

I managed to get the authentication part working but the authorization part is not working(towards end of tutorial). If I add the jwt token with the word bearer in the authorization header it gives me 401 authorization denied.

I'm thinking maybe I need to create a custom authorization attribute.

  1. Is there any way to use the existing authorize attribute?
  2. What does the existing Authorize attribute look for in order to authorize a user(not including roles or users parameters in the authorize attribute) ?

Startup.Auth.cs

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    // For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        var issuer = ConfigurationManager.AppSettings["Issuer"];
        var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["Secret"]);

        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
        {
            AuthenticationMode = AuthenticationMode.Active,
            AllowedAudiences = new[] { "Any" },
            IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
            {
                new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
            }
        });

        app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
        {
            AllowInsecureHttp = true,
            TokenEndpointPath = new PathString("/Token"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            Provider = new CustomOAuthProvider(),
            AccessTokenFormat = new CustomJwtFormat(issuer)
        });
    }
}

CustomOAuthProvider

public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
    public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

        var blSecurity = new BLSecurity();
        var user = blSecurity.LogonUser(context.UserName, context.Password);

        if (!(user.ResponseType == Global.Response.ResponseTypes.Success))
        {
            context.SetError("Authentication Error", "The user name or password is incorrect");
            return Task.FromResult<object>(null);
        }

        var ticket = new AuthenticationTicket(SetClaimsIdentity(context, user.LoggedOnUser), new AuthenticationProperties());
        context.Validated(ticket);

        return Task.FromResult<object>(null);
    }

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

    private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, User user)
    {
        //Add User Claims
        var identity = new ClaimsIdentity("JWT");
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("sub", context.UserName));
        identity.AddClaim(new Claim("bn", user.BranchName));
        identity.AddClaim(new Claim("fn", user.FirstName));
        identity.AddClaim(new Claim("ln", user.LastName));

        //Add User Role Claims
        var blRole = new BLRole();
        var roles = blRole.GetRolesByUserId(user.UserID);

        if (roles != null && roles.Count > 0)
        {
            foreach (var role in roles)
            {
                identity.AddClaim(new Claim(ClaimTypes.Role, role.RoleName));
            }
        }

        return identity;
    }
}

CustomJwtFormat

public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
    private static readonly byte[] _secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
    private readonly string _issuer;

    public CustomJwtFormat(string issuer)
    {
        _issuer = issuer;
    }

    public string Protect(AuthenticationTicket data)
    {
        if (data == null)
        {
            throw new ArgumentNullException(nameof(data));
        }

        var signingKey = new HmacSigningCredentials(_secret);
        var issued = data.Properties.IssuedUtc;
        var expires = data.Properties.ExpiresUtc;

        return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime.ToLocalTime(), expires.Value.UtcDateTime.ToLocalTime(), signingKey));
    }

    public AuthenticationTicket Unprotect(string protectedText)
    {
        throw new NotImplementedException();
    }
}
1
if i add the jwt token with the word bearer - dont forget about space between Bearer and token. Like Bearer mytokendata. And did you checked your token here? - tym32167
@tym32167 i used a space between bearer and token and yes i have checked the token on jwt.io and its valid - JoshuaDhanapalan
Btw. did you read the comments in that tutorial. Other people also had problems for which there's a solution in the comments as it seems. - jps
I forgot to mention that i changed the authentication bit to look at an existing users table instead of the aspnetusers table i will show code now - JoshuaDhanapalan
did you tried what was recommended in comments from that article? For examlple new JwtSecurityToken(_issuer, null, data.Identity.Claims,... -> new JwtSecurityToken(_issuer, “Any”, data.Identity.Claims,... - tym32167

1 Answers

1
votes

I managed to solve it by applying what a guy posted in the comments section. see below quoted.

Roman Shramko June 23, 2016 There’s kinda a bug in the code above. The JwtBearerAuthenticationOptions is configured with

AllowedAudiences = new[] { “Any” },

but in fact, the token content does not contain any audience, so that your request gets rejected.

The fastest way to fix that (and not the best one), is to change the way you create a token in the Protect method of the CustomJwtFormat class from

new JwtSecurityToken(_issuer, null, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);

to this one

new JwtSecurityToken(_issuer, “Any”, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);

i.e. to pass “Any” instead of null as a second constructor parameter.