1

I have created simple Web application with Login mechanism using JSON Web Token (JWT). I generate TOKEN in my Web API and then, store it using Redux storage, so I can access it anywhere I want. It looks like this, I am using .subscribe() to track any changes in my loginStore:

  const loginStore = createStore(loginUserReducer);
  //After I log in, I put TOKEN to storage and reload my app
  loginStore.subscribe(() => {
    loginStore.getState().then(x => {
      if(x) { 
        localStorage.setItem('TOKEN', x);
      } else {
        localStorage.clear();
      }
      window.location.reload();
    });
  });

I created my Web API in .Net Framework 4.7.2. Next, I would like to use AuthorizeAttribute for authorization in my HomeController to protect my API from any unwanted calls:

public class HomeController : ApiController
{
    List<string> myList = new List<string>
    {
        "Element1",
        "Element2",
        "Element3"
    };

    [Authorize]//I use this for authorization -> using System.Web.Http;
    [HttpGet]
    [Route("api/mylist")]
    public List<string> MyList()
    {
        return this.myList;
    }
}

In the end, I use fetch() method and try to get data from myList from API and show it in console.log() at front.

  fetch('https://localhost:XXXXX/api/mylist', {
    method: 'GET',
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json'
    },
  }).then(x => {
    console.log(x.json());//Outcome from API
  }).catch(err => {
      console.log(err);
  });

What I get is an error message:

"Authorization has been denied for this request."

QUESTION: How do I pass my token/user data/whatever from React to Web API, so AuthorizeAttribute will allow it and return data I want ?

Any solution will be appreciated.

I am not using any user roles right now, but solution with custom attribute and roles validation will be event better :)

1 Answer 1

1

Alright! I figured it out and it is actually really easy! I will add a long answer in case someone will need it.

Step 1: Use System.IdentityModel.Tokens.Jwt, Microsoft.AspNet.WebApi.Cors and Microsoft.AspNet.Cors for TOKEN generation.

Step 2: Implement static class TokenManager where you will generate/validate TOKEN and place there you user roles. (Implementation source)

public static class TokenManager
{
    private static string Secret = "my_secret_key";
    //Use
    private static JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();

    public static string GenerateToken(string username)
    {
        byte[] key = Convert.FromBase64String(Secret);
        var descriptor = GenerateTokenDescriptor(username, key);

        JwtSecurityToken token = handler.CreateJwtSecurityToken(descriptor);
        return handler.WriteToken(token);
    }

    private static SecurityTokenDescriptor GenerateTokenDescriptor(string username, byte[] key)
    {
        SymmetricSecurityKey securityKey = new SymmetricSecurityKey(key);

        SecurityTokenDescriptor descriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[] {
                new Claim(ClaimTypes.Name, username),
                new Claim(ClaimTypes.Role, "Role1"),//<-- User role!
                new Claim(ClaimTypes.Role, "Role2")}),//<-- User role!
            Expires = DateTime.UtcNow.AddMinutes(30),//Token takes only UTC time
            SigningCredentials = new SigningCredentials(securityKey,
            SecurityAlgorithms.HmacSha256Signature)
        };

        return descriptor;
    }

    public static ClaimsPrincipal GetPrincipal(string token)
    {
        try
        {
            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = (JwtSecurityToken)tokenHandler.ReadToken(token);
            if (jwtToken == null)
                return null;
            byte[] key = Convert.FromBase64String(Secret);
            TokenValidationParameters parameters = new TokenValidationParameters()
            {
                RequireExpirationTime = true,
                ValidateIssuer = false,
                ValidateAudience = false,
                IssuerSigningKey = new SymmetricSecurityKey(key)
            };
            SecurityToken securityToken;
            ClaimsPrincipal principal = tokenHandler.ValidateToken(token,
                  parameters, out securityToken);
            return principal;
        }
        catch (Exception e)
        {
            return null;
        }
    }
}

NOTE: I have generated my_secret_key using this code:

HMACSHA256 hmac = new HMACSHA256();
string key = Convert.ToBase64String(hmac.Key);

Step 3: Create Custom attribute which will be used for authorization.

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    private readonly string[] allowedroles;
    public MyAuthorizeAttribute(params string[] roles)
    {
        this.allowedroles = roles;
    }
    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        //Default outcome 
        bool authorize = false;
        //Get TOKEN
        var authToken = actionContext.Request.Headers.Authorization?.Parameter;
        //Check if TOKEN has parameters
        if (authToken != null)
        {
            //Get roles from TOKEN
            List<string> userRoles = TokenManager.GetPrincipal(authToken).FindAll(ClaimTypes.Role).Select(x => x.Value).ToList();
            //Check if any of User Roles is allowed
            authorize = this.allowedroles.Any(x => userRoles.Any(y => y == x));
        }
        //return outcome
        return authorize;
    }
}

Step 4: Use your attribute in Controller

public class HomeController : ApiController
{
    List<string> myList = new List<string>
    {
        "Element1",
        "Element2",
        "Element3"
    };

    [MyAuthorizeAttribute("Role2")]//Add roles names in parameter
    [HttpGet]
    [Route("api/mylist")]
    public List<string> MyList()
    {
        return this.myList;
    }

    [HttpPost]
    [Route("api/login")]
    public HttpResponseMessage Login()
    {
        var myToken = TokenManager.GenerateToken("username");
        return Request.CreateResponse(HttpStatusCode.OK, myToken);
    }
}

Step 5: Save TOKEN in LOCAL STORAGE:

fetch('https://localhost:XXXXX/api/login', {
        method: 'post',
        headers: {
            Accept: 'application/json',
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        },
      }).then(x => {
          x.json().then(y => {
            localStorage.setItem('TOKEN', y);
            });
      }).catch(err => {
          console.log(err);
      });

Step 6: In the end, pass your TOKEN in the header of your request:

  fetch('https://localhost:XXXXX/api/mylist', {
    method: 'GET',
    headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + localStorage.getItem('TOKEN'),
        'Access-Control-Allow-Origin': '*'
    },
  }).then(x => {
    console.log(x.json());//Outcome from API
  }).catch(err => {
      console.log(err);
  });

Done, should work just fine.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.