How to Store Tokens in ASP.NET Core Identity

How To Store Tokens in ASP.NET Core Identity Database Table

In this article, I will discuss how to Store Tokens in the ASP.NET Core Identity Database Table. Please read our previous article discussing how to implement the Forgot Password Functionality in ASP.NET Core Identity. In ASP.NET Core Identity, tokens are used for actions like email confirmation and password reset. By default, these tokens are short-lived and not stored in the database, but sometimes storing them can provide more control and flexibility.

Why the Email Confirmation and Forgot Password Tokens Are Not Stored in the AspNetUserTokens Table?

In ASP.NET Core Identity, the UserTokens (AspNetUserTokens) table is designed to store tokens for various purposes, such as Authentication, Two-Factor Authentication (2FA), etc. However, Email Confirmation and Forgot Password tokens are not stored in the UserTokens (AspNetUserTokens) table by default. The following are the primary reasons for this design choice:

Security Considerations:
  • Storing sensitive tokens like Email Confirmation and Forgot Password in the database could expose security risks. If the database is compromised, attackers could gain access to these tokens, potentially allowing unauthorized changes to email addresses or passwords.
  • That means the attacker can confirm email addresses or reset passwords without the user’s knowledge or consent.
  • By not persisting sensitive tokens, ASP.NET Core Identity reduces the risk of token theft or misuse.
One-Time Use and Short Lifespan:
  • Both Email Confirmation and Forgot Password tokens are designed to be one-time use and typically have a short lifespan (usually minutes to an hour).
  • They are meant to be used quickly after generation, so storing them in a database is often unnecessary and inefficient, as they become invalid after a single use or upon expiration.
Stateless Approach:
  • ASP.NET Core Identity uses a stateless approach for these tokens. Instead of storing the token, it generates the token dynamically using user-specific data (e.g., user ID, email) combined with a secret key.
  • When the token is submitted for verification, the system regenerates it using the same parameters and secret key and validates it against the provided token.
  • Hence, a database lookup is not required. This eliminates the need for storing the token in a database, reducing complexity and database dependency.

Key Reason for Not Storing in the Database: These tokens are one-time-use, time-bound, and generated using a deterministic algorithm, hence no need to persist them unless you want more control (like revocation or audit logging).

How are Tokens Generated and Validated?

ASP.NET Core Identity uses a Token Provider Pattern to generate short-lived tokens for specific operations like email confirmation and password reset. These tokens are:

  • Self-Contained & Signed: The token encodes the User Identity + Purpose + Security Stamp + Timestamp and is generated using a secure hashing algorithm.
  • Time-limited: The token is validated against a timestamp and expires after a configured timespan.
  • Server-verifiable without DB lookups: Upon receipt, the server can regenerate the expected token at validation time using the same inputs and compare it with the one received from the user, eliminating the need to store the token.
What is the AspNetUserTokens Table?

The AspNetUserTokens (in our example, UserTokens) table is part of the ASP.NET Core Identity schema. It is designed to store custom tokens associated with a user, with the following columns:

  • UserId: Foreign key to the user.
  • LoginProvider: The name of the system or app that issued the token (e.g., “MyApp”).
  • Name: A label for the token (e.g., “ForgotPasswordToken”).
  • Value: The actual token string.

This table is handy when we need to persist user-specific tokens for reuse, validation, revocation, or auditing purposes.

Why Use AspNetUserTokens to Store Tokens?

By default, ASP.NET Core Identity tokens (e.g., for password reset, email confirmation) are not stored in the database; they are generated and validated using cryptographic operations. This is secure and stateless but comes with limitations:

You might want to store tokens if:

  • You want to manually revoke or invalidate a token (e.g., expire a token early).
  • You want to audit or track the token usage.
  • You want to allow the user to regenerate or re-use a token within its lifetime.
  • You are building a custom workflow, like manual password reset approval, multi-step onboarding, or phone number verification.

Using AspNetUserTokens, you can persist and control tokens just like you control database records.

How Token Storage Works with UserManager

ASP.NET Core Identity provides built-in methods on UserManager<TUser> to interact with the AspNetUserTokens table:

  • SetAuthenticationTokenAsync: It stores a token for a specific user, under a custom provider and key name. It adds or updates a row in AspNetUserTokens.
  • GetAuthenticationTokenAsync: It retrieves the stored token value for validity comparison and to facilitate reset or confirmation operations.
  • RemoveAuthenticationTokenAsync: It deletes the stored token. That is used to invalidate or revoke a token.
Storing Forgot Password Token in Database:

Now, we will modify our existing SendPasswordResetLinkAsync and ResetPasswordAsync methods to:

  • Store the reset token in the AspNetUserTokens (UserTokens) table using SetAuthenticationTokenAsync
  • Retrieve and use it securely
  • Remove it after a successful password reset

Let us proceed and implement this step by step:

Modify SendPasswordResetLinkAsync

Now, we will modify SendPasswordResetLinkAsync to Store Token

  • Generate token
  • Store token in AspNetUserTokens using SetAuthenticationTokenAsync
  • Then send the link via email

So, please update the SendPasswordResetLinkAsync method as follows:

public async Task<bool> SendPasswordResetLinkAsync(string email)
{
    // Try to find the user by their email address
    var user = await _userManager.FindByEmailAsync(email);

    // Security measure: 
    // Do not reveal whether the user exists or not — 
    // always behave the same if the user is not found or the email is not confirmed
    if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
        return false;

    // Generate a unique, secure token for password reset
    var token = await _userManager.GeneratePasswordResetTokenAsync(user);

    // Save token in AspNetUserTokens table

    // Delete old token if any (only 1 active at a time)
    // SetAuthenticationTokenAsync will overwrite the value for the same (UserId, LoginProvider, Name) composite key.
    // await _userManager.RemoveAuthenticationTokenAsync(user, "ResetPassword", "PasswordResetToken");

    // Save new token
    await _userManager.SetAuthenticationTokenAsync(user, "ResetPassword", "PasswordResetToken", token);

    // Encode the token so it can be safely used in a URL
    var encodedToken = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token));

    // Construct the password reset link with the encoded token and user’s email
    var baseUrl = _configuration["AppSettings:BaseUrl"];
    var resetLink = $"{baseUrl}/Account/ResetPassword?email={user.Email}&token={encodedToken}";

    // Send the reset link via email to the user
    await _emailService.SendPasswordResetEmailAsync(user.Email!, user.FirstName, resetLink);

    return true;
}
Modify ResetPasswordAsync

Now, we will modify ResetPasswordAsync to retrieve and remove the Token

  • Decode token
  • Retrieve token from database using GetAuthenticationTokenAsync
  • Compare tokens
  • Reset password
  • Delete token using RemoveAuthenticationTokenAsync on success

So, please update the ResetPasswordAsync method as follows:

public async Task<IdentityResult> ResetPasswordAsync(ResetPasswordViewModel model)
{
    // Find the user associated with the provided email
    var user = await _userManager.FindByEmailAsync(model.Email);

    // If user not found, return a generic failure (no details leaked for security)
    if (user == null)
        return IdentityResult.Failed(new IdentityError { Description = "Invalid request." });

    // Decode the token that was passed in from the reset link
    var decodedBytes = WebEncoders.Base64UrlDecode(model.Token);
    var decodedToken = Encoding.UTF8.GetString(decodedBytes);

    // Get stored token from AspNetUserTokens
    var storedToken = await _userManager.GetAuthenticationTokenAsync(user, "ResetPassword", "PasswordResetToken");

    if (string.IsNullOrEmpty(storedToken) || storedToken != decodedToken)
        return IdentityResult.Failed(new IdentityError { Description = "Invalid or expired token." });

    // Attempt to reset the user's password with the new one
    var result = await _userManager.ResetPasswordAsync(user, decodedToken, model.Password);

    // If successful, update the Security Stamp to invalidate any active sessions or tokens
    if (result.Succeeded)
    {
        // Invalidate sessions
        await _userManager.UpdateSecurityStampAsync(user);

        // Delete token after successful use
        await _userManager.RemoveAuthenticationTokenAsync(user, "ResetPassword", "PasswordResetToken");
    }

    return result;
}
What Is the Default Token Lifetime?

In ASP.NET Core Identity:

  • By default, Identity uses the DataProtectorTokenProvider<TUser> for generating Email Confirmation and Password Reset Tokens.
  • The Default Lifetime of these tokens is 1 day (24 hours).
  • This means that if a user doesn’t use their reset or confirmation link within 24 hours, the token will be considered invalid.
  • The lifetime is enforced at validation time (e.g., ConfirmEmailAsync, ResetPasswordAsync), which compares the token’s embedded issuance timestamp against the current options.

Note: Other token providers may have different defaults. For example, the PhoneNumberTokenProvider (used for SMS verification) typically issues short-lived tokens (often just a few minutes). So, unless you explicitly configure it, the reset and email confirmation tokens will expire in 24 hours.

How to Set Token life time in ASP.NET Core Identity?

To customize the lifetime of these tokens, you need to configure the DataProtectionTokenProviderOptions. You typically need to do this in the Program.cs class file as follows:

// Configure token lifespan for DataProtectionTokenProvider
builder.Services.Configure<DataProtectionTokenProviderOptions>(options =>
{
    // Example: tokens valid for 2 hours
    options.TokenLifespan = TimeSpan.FromHours(2);
});
Code Explanation
  • DataProtectionTokenProviderOptions.TokenLifespan → defines how long the token is valid.
  • By changing this setting, we control how quickly password reset/email confirmation links expire.
  • For production:
    • Shorter lifespan (e.g., 30 minutes – 2 hours) → more secure.
    • Longer lifespan (e.g., 24–48 hours) → more user-friendly, especially for email links that may be delayed.

Storing tokens in the AspNetUserTokens table allows developers to manage, revoke, or track tokens more effectively. This approach gives extra security and control while still supporting the default Identity features.

Task? How to set a different lifespan for different providers.

In the next article, I will discuss How to Change Password in ASP.NET Core Identity. In this article, I explain how to Store Tokens in ASP.NET Core Identity. I hope you enjoy this article, How to Store Tokens in ASP.NET Core Identity.

Leave a Reply

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