Building Secure APIs with JWT Access and Refresh Tokens

Building Secure APIs with JWT Access and Refresh Tokens

When building secure APIs, implementing authentication and session management is crucial. Access tokens and refresh tokens are common solutions to ensure security, scalability, and user-friendly experiences. In this blog, we’ll explain how to use these tokens in a .NET WebForms application with step-by-step guidance.

Understanding Access and Refresh Tokens

Access Token

  • A short-lived token (e.g., 5 minute) used to authenticate API requests.
  • Passed in the Authorization header of every API call.

Refresh Token

  • A long-lived token (e.g., 3 days) used to generate a new access token.
  • Stored securely (e.g., database) and only sent to specific endpoints.

By combining these tokens, you can secure your APIs and provide a seamless experience for users.

Database Setup

Create a table to store refresh tokens:

 CREATE TABLE RefreshTokens (
    id INT PRIMARY KEY IDENTITY,
    userId INT NOT NULL,
    token NVARCHAR(500) NOT NULL,
    expiresAt DATETIME NOT NULL,
    createdAt DATETIME DEFAULT getdate(),
    revokedAt DATETIME NULL,
    isRevoked BIT DEFAULT 0
);  Stored procedure to insert a token
 Create procedure  [usp_insertRefreshToken](@userId int, @refreshToken nvarchar(500))
as
begin
SET NOCOUNT ON;
 
    INSERT INTO RefreshTokens (userId, token, expiresAt, createdAt, revokedAt, isRevoked)
    VALUES (
        @userId,
        @refreshToken,
        DATEADD(DAY, 3, getdate()), 
        getdate, 
        NULL,  
        0  
    );
end 

Stored Procedure to Invalidate Refresh Tokens

 CREATE PROCEDURE usp_InvalidateRefreshToken
    @token NVARCHAR(500)
AS
BEGIN
    UPDATE RefreshTokens
    SET isRevoked = 1, revokedAt = getdate()
    WHERE token = @token AND isRevoked = 0;
END; 

First add JWT Support

Install the required NuGet package:

Use the System.IdentityModel.Tokens.Jwt library to handle JWT creation and validation.

Run this command in the Package Manager Console:

 Install-Package System.IdentityModel.Tokens.Jwt  

Implementing Token Generation

Access Token Generation

Use the JwtSecurityToken class to generate an access token:

 public static string GenerateAccessToken(string userId)
{
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: issuer,
        audience: audience,
        claims: new[]
        {
            new Claim(ClaimTypes.NameIdentifier, userId),
            new Claim("role", "user")
        },
        expires: DateTime.UtcNow.AddMinutes(15), //customize this expiry
        signingCredentials: creds
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
} 

Refresh Token Generation

Generate a random refresh token:

 public static string GenerateRefreshToken()
{
    var randomNumber = new byte[32];
    using (var rng = RandomNumberGenerator.Create())
    {
        rng.GetBytes(randomNumber);
    }
    return Convert.ToBase64String(randomNumber);
}

Save the refresh token to the database:

 public void SaveRefreshToken(int userId, string refreshToken)
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["yourconnectionstring"].ConnectionString))
    {
        using (SqlCommand cmd = new SqlCommand("INSERT INTO RefreshTokens
        (userId, token, expiresAt) VALUES (@userId, @token, @expiresAt)", conn))
        {
            cmd.Parameters.AddWithValue("@userId", userId);
            cmd.Parameters.AddWithValue("@token", refreshToken);
            cmd.Parameters.AddWithValue("@expiresAt", DateTime.UtcNow.AddDays(3));

            conn.Open();
            cmd.ExecuteNonQuery();
        }
    }
} 

Token Validation
Validating Access Tokens

To verify access tokens on every API request:

 public static ClaimsPrincipal ValidateAccessToken(string token)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.UTF8.GetBytes(secretKey);

            var validationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = issuer,
                ValidAudience = audience,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            };

            try
            {
                SecurityToken validatedToken;
                return tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
            }
            catch
            {
                return null; // Token is invalid
            }
        }  

To revoke a refresh token:

 public bool InvalidateRefreshToken(string refreshToken)
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["yourconnectionstring"].ConnectionString))
    {
        using (SqlCommand cmd = new SqlCommand("usp_InvalidateRefreshToken", conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@token", refreshToken);

            conn.Open();
            int rowsAffected = cmd.ExecuteNonQuery();
            return rowsAffected > 0;
        }
    }
} 

Integrating Token Verification in APIs

  string authHeader = Request.Headers["Authorization"];
        if (authHeader == null || JWTHandler.ValidateAccessToken(authHeader.Replace("Bearer ", "")) == null)
        {
            throw new UnauthorizedAccessException("Invalid or missing token.");
        } 

Refreshing Access Tokens

If the access token expires, generate a new one using the refresh token:

 public string RefreshAccessToken(string refreshToken)
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString))
    {
        string query = "SELECT userId, expiresAt, isRevoked FROM RefreshTokens WHERE token = @token";

        using (SqlCommand cmd = new SqlCommand(query, conn))
        {
            cmd.Parameters.AddWithValue("@token", refreshToken);
            conn.Open();

            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read() && !(bool)reader["isRevoked"] && (DateTime)reader["expiresAt"] > DateTime.UtcNow)
                {
                    string userId = reader["userId"].ToString();
                    return JWTHandler.GenerateAccessToken(userId);
                }
                else
                {
                    throw new UnauthorizedAccessException("Invalid or expired refresh token.");
                }
            }
        }
    }
} 

Conclusion

By combining access and refresh tokens, you can ensure secure, scalable APIs while enhancing the user experience. This approach minimizes the risk of unauthorized access and provides a reliable mechanism for session management. With the above implementation, your .NET WebForms application is ready to handle secure API requests seamlessly.


Interoons aim at providing electronically intelligent and comprehensive range of digital marketing solutions that exceed customer expectations. We implement revolutionary digital marketing ideas to achieve a common as well as the aggregate growth of the organization. Long-term customer relations and extended support are maintained.

Leave a Reply

Your email address will not be published. Required fields are marked *