Sunday, October 18, 2020

[.NET core] AddPolicy to Authorize on MVC controller

Sometimes when you are using C# identity, the [Authorize] attribute is not enough,for example, you want additional check not only claims or role, then you can try add policy to make it. 

In this example, we assume a part of method need check token in user claims before access, here is how to implement it. 

1.add a AuthorizationHandler for your policy 
 Handler/AuthorizeTokenHandler.cs
namespace Example.Handler
{
    // Custom AuthorizationHandler for check token in claim
    public class AuthorizeTokenHandler : AuthorizationHandler<TokenRequirement>
    {
        private readonly AuthService _auth;

        public AuthorizeTokenHandler(AuthService auth)
        {
            _auth = auth;
        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TokenRequirement requirement)
        {
            //no token claim
            if (!context.User.HasClaim(x => x.Type == "Token"))
            {
                context.Fail();
                return Task.CompletedTask;
            }

            var username = context.User.FindFirst(ClaimTypes.Name).Value;
            var token = context.User.FindFirst("Token").Value;

            #region check token
            if (_auth.CheckToken(username,token))
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
            #endregion

            return Task.CompletedTask;
        }
    }

    public class TokenRequirement : IAuthorizationRequirement
    {
    }

2. add to startup, in ConfigureServices

            //handler
            services.AddSingleton<IAuthorizationHandler, AuthorizeTokenHandler>();
            
            //add policy
            services.AddAuthorization(options =>
            {
                options.AddPolicy("TokenRequire", policy =>
                    policy.Requirements.Add(new TokenRequirement()));
            });

3. apply policy to Authorize when you need

[Authorize(Policy = "TokenRequire")]
[HttpPost]
public ApiResult SomeMethod()
{
}

That's all, enjoy it.

Sunday, October 11, 2020

[C#] Best Practices of Get Claim from Identity

If you are using ASP.NET Identity, whatever using ASP.NET Core Identy or ASP.NET Identity, usually you will add some claims into the identity and use it later, but I saw lot people using unsafe way to get claim value from identity, it may cause uncatch error if the claim type not exists, let's see the code.
//set some claims first
var claims = new List<Claim>
{
     new Claim(ClaimTypes.Name,"test name"),
     new Claim("Token","token123"),
     new Claim("Number","3")
     //new Claim("dummy","dummy string")
};
var ci = new ClaimsIdentity(claims);

//most seen way, throw error if not exists
var name = ci.Claims.First(x => x.Type == ClaimTypes.Name).Value;
var token = ci.Claims.First(x => x.Type == "Token").Value;  
//using FirstOrDefault() only, throw error if not exists
//var dunno = ci.Claims.FirstOrDefault(x => x.Type == "dunno").Value;

Using this way is easy, but if the claim not exists, if will cause error, whatever you are using First() or FirstOrDefault(). If want to using FirstOrDefault() to get claim value, you can using this fixed way.

            //fixed FirstOrDefault way: return null if not exists
            var dunno = ci.Claims.Where(x => x.Type == "dunno").Select(x => x.Value).FirstOrDefault();
            

but you can use HasClaim method to check claim exist first, and give a proper value if it not exists.

            //safe way: using HasClaim to check
            var dummy = ci.HasClaim(x => x.Type == "dummy")
                ? ci.Claims.First(x => x.Type == "dummy").Value
                : null;
            

or you can choose this short way by using FindFirst method.

            //short way: using FindFirst
            var dummyShort = ci.FindFirst("dummy")?.Value;
            //short way for int
            int.TryParse(ci.FindFirst("Number")?.Value, out var number);
            

I think use ClaimsIdentity.FindFirst Method is the best practices for get claim value.
You can test those here in here