.NET Core Hand-crafting a Token-based Permission Authentication

.NET Core Hand-crafting a Token-based Permission Authentication

Permission authentication is the process of determining a user's identity and whether they have the authority to access resources.

Last updated 7/9/2022 9:18 PM
黑哥聊dotNet
5 min read
Category
.NET
Tags
.NET C#

Description

Permission authentication is the process of determining a user's identity and whether they have the right to access resources.

Today, I'll share with you a token-based authentication mechanism similar to JWT.

The token-based authentication mechanism does not require the server to retain the user's authentication information or session information. This means that applications using token-based authentication do not need to consider which server the user logged in on, providing convenience for application scaling.

The flow is as follows:

  1. The user requests the server with a username and password.
  2. The server verifies the user's information.
  3. The server sends a token to the user upon successful verification.
  4. The client stores the token and attaches it with each request.
  5. The server validates the token and returns the data.

Today, I'll handcraft an authentication mechanism similar to Jwt.

Demo

Create an Authorization Filter Inheriting from IAuthorizationFilter

public class ApiAuthorize : IAuthorizationFilter
{}

Create an Attribute for Requiring Authorization and an Attribute for Allowing Unauthenticated Access

public class MyAuthentication:Attribute, IFilterMetadata
{
}

public class MyNoAuthentication : Attribute, IFilterMetadata
{
}

We need to check in our authorization filter whether the request header contains the attribute for requiring authorization or the attribute for allowing unauthenticated access. If it has the attribute for allowing unauthenticated access, proceed directly to the next pipeline. If it has the attribute for requiring authorization, perform token validation.

public class ApiAuthorize : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (context.Filters.Contains(new MyNoAuthentication()))
        {
            return;
        }
        var authorize = context.HttpContext.Request.Headers["MyAuthentication"];
        if (string.IsNullOrWhiteSpace(authorize))
        {
            context.Result = new JsonResult("Request authorization cannot be empty");
            return;
        }
        if (!MemoryCacheHelper.Exists(authorize))
        {
            context.Result = new JsonResult("Invalid authorization information or authorization information expired");
            return;
        }
    }
}

Some of you may have noticed: how are tokens stored and retrieved? A common approach is to use Cache. I won't go into too much detail here; if you're interested, you can check out my previous article.

CacheHelper Code

public class MemoryCacheHelper
{

    public static MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

    /// <summary>
    /// Verify if a cache item exists
    /// </summary>
    /// <param name="key">Cache key</param>
    /// <returns></returns>
    public static bool Exists(string key)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }
        return _cache.TryGetValue(key, out _);
    }

    /// <summary>
    /// Get a cache item
    /// </summary>
    /// <param name="key">Cache key</param>
    /// <returns></returns>
    public static object Get(string key)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }
        if (!Exists(key))
            throw new ArgumentNullException(nameof(key));
        return _cache.Get(key);
    }

    /// <summary>
    /// Add a cache item
    /// </summary>
    /// <param name="key">Cache key</param>
    /// <param name="value">Cache value</param>
    /// <param name="expiresSliding">Sliding expiration duration (if there is an operation within the expiration time, extend the expiration time from the current time)</param>
    /// <param name="expiressAbsoulte">Absolute expiration duration</param>
    /// <returns></returns>
    public static bool AddMemoryCache(string key, object value)
    {
        if (key == null)
        {
            throw new ArgumentNullException(nameof(key));
        }
        if (value == null)
        {
            throw new ArgumentNullException(nameof(value));
        }

        _cache.Set(key, value,
        new MemoryCacheEntryOptions()
        {
            SlidingExpiration = new TimeSpan(0, 0, 10),
            Priority = CacheItemPriority.NeverRemove,
            AbsoluteExpiration = DateTime.Now.AddMinutes(1)
        });
        return Exists(key);
    }
}

The authentication code is basically complete. Let's go back to the flow.

The user requests the server with a username and password. The server verifies the user's information. The server sends a token to the user upon successful verification.

For commercial software, most interfaces require authorization to use. So we register it globally in the StartUp class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(o=>
    {
        o.Filters.Add<ApiAuthorize>();
        o.Filters.Add<MyAuthentication>();
    });
}

I won't explain the token generation principle in detail here. In this example, we use AES encryption to generate the token. On the server-side token request endpoint, we add the attribute for allowing unauthenticated access, then issue a token.

[HttpGet("GetToken")]
[MyNoAuthentication]
public IActionResult GetToken(string UserCode)
{
   string token=  AESEncrypt.Encrypt(UserCode);
   MemoryCacheHelper.AddMemoryCache(token, User);
   return Ok(token);
}

Use Postman to request token generation, as shown in the image.

Then, add an interface that requires authorization. Since we already registered the global authorization attribute, we don't need to add it again.

[HttpGet("GetUserInformation")]
public IActionResult GetUserInformation()
{
    return Ok(new { Name="123",Age=18,Sex="Gender"});
}

Request this interface without a token using Postman, as shown in the image.

Request this interface with a token using Postman, as shown in the image.

Through the example above, we have clearly understood the process of permission authentication. That concludes today's introduction.

Finally, if you like my articles, please give me a follow and a thumbs up. I hope the .NET ecosystem gets better and better!

Keep Exploring

Related Reading

More Articles