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
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.
Recommended Posts

Enhancing Social Media Sharing in ASP.NET Web Forms
February 10, 2025

How to Use Native Modules in React Native (Android/Hybrid)
February 5, 2025

Implementing Google reCAPTCHA
January 28, 2025