186 lines
7.2 KiB
C#
186 lines
7.2 KiB
C#
using System;
|
|
|
|
namespace EonaCat.SecureToken.Core
|
|
{
|
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
|
|
/// <summary>
|
|
/// Represents an access token and refresh token pair.
|
|
/// </summary>
|
|
public sealed class AccessTokenRefreshTokenPair
|
|
{
|
|
/// <summary>
|
|
/// The access token (short-lived, typically for API requests).
|
|
/// </summary>
|
|
public string AccessToken { get; set; }
|
|
|
|
/// <summary>
|
|
/// The refresh token (long-lived, used to obtain new access tokens).
|
|
/// </summary>
|
|
public string RefreshToken { get; set; }
|
|
|
|
/// <summary>
|
|
/// Creates a new token pair.
|
|
/// </summary>
|
|
public AccessTokenRefreshTokenPair(string accessToken, string refreshToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(accessToken))
|
|
{
|
|
throw new ArgumentException("Access token cannot be null or empty.", nameof(accessToken));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(refreshToken))
|
|
{
|
|
throw new ArgumentException("Refresh token cannot be null or empty.", nameof(refreshToken));
|
|
}
|
|
|
|
AccessToken = accessToken;
|
|
RefreshToken = refreshToken;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper for issuing paired access and refresh tokens with optimal defaults.
|
|
/// </summary>
|
|
public static class TokenPairDescriptor
|
|
{
|
|
/// <summary>
|
|
/// Default lifetime for access tokens: 15 minutes.
|
|
/// </summary>
|
|
public static readonly TimeSpan DefaultAccessTokenLifetime = TimeSpan.FromMinutes(15);
|
|
|
|
/// <summary>
|
|
/// Default lifetime for refresh tokens: 30 days.
|
|
/// </summary>
|
|
public static readonly TimeSpan DefaultRefreshTokenLifetime = TimeSpan.FromDays(30);
|
|
|
|
/// <summary>
|
|
/// Creates an access token and refresh token pair with secure defaults.
|
|
/// </summary>
|
|
/// <param name="service">The token service</param>
|
|
/// <param name="subject">The subject (user ID, etc.)</param>
|
|
/// <param name="issuer">The issuer</param>
|
|
/// <param name="audience">The audience</param>
|
|
/// <param name="accessTokenLifetime">Custom access token lifetime; defaults to 15 minutes</param>
|
|
/// <param name="refreshTokenLifetime">Custom refresh token lifetime; defaults to 30 days</param>
|
|
/// <returns>A pair of access and refresh tokens</returns>
|
|
public static AccessTokenRefreshTokenPair CreatePair(
|
|
ITokenService service,
|
|
string subject,
|
|
string issuer,
|
|
string audience,
|
|
TimeSpan? accessTokenLifetime = null,
|
|
TimeSpan? refreshTokenLifetime = null)
|
|
{
|
|
if (service is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(service));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(subject))
|
|
{
|
|
throw new ArgumentException("Subject cannot be null or empty.", nameof(subject));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(issuer))
|
|
{
|
|
throw new ArgumentException("Issuer cannot be null or empty.", nameof(issuer));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(audience))
|
|
{
|
|
throw new ArgumentException("Audience cannot be null or empty.", nameof(audience));
|
|
}
|
|
|
|
var accessLifetime = accessTokenLifetime ?? DefaultAccessTokenLifetime;
|
|
var refreshLifetime = refreshTokenLifetime ?? DefaultRefreshTokenLifetime;
|
|
|
|
var accessToken = service.Issue(
|
|
TokenDescriptor.Create()
|
|
.ForSubject(subject)
|
|
.IssuedBy(issuer)
|
|
.ForAudience(audience)
|
|
.WithLifetime(accessLifetime));
|
|
|
|
var refreshToken = service.Issue(
|
|
TokenDescriptor.Create()
|
|
.ForSubject(subject)
|
|
.IssuedBy(issuer)
|
|
.ForAudience(audience)
|
|
.AsRefreshToken()
|
|
.WithLifetime(refreshLifetime));
|
|
|
|
return new AccessTokenRefreshTokenPair(accessToken, refreshToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an access token and refresh token pair with custom claims and roles.
|
|
/// </summary>
|
|
/// <param name="service">The token service</param>
|
|
/// <param name="subject">The subject (user ID, etc.)</param>
|
|
/// <param name="issuer">The issuer</param>
|
|
/// <param name="audience">The audience</param>
|
|
/// <param name="configure">Optional action to configure additional claims and roles</param>
|
|
/// <param name="accessTokenLifetime">Custom access token lifetime; defaults to 15 minutes</param>
|
|
/// <param name="refreshTokenLifetime">Custom refresh token lifetime; defaults to 30 days</param>
|
|
/// <returns>A pair of access and refresh tokens</returns>
|
|
public static AccessTokenRefreshTokenPair CreatePair(
|
|
ITokenService service,
|
|
string subject,
|
|
string issuer,
|
|
string audience,
|
|
Action<TokenDescriptor>? configure = null,
|
|
TimeSpan? accessTokenLifetime = null,
|
|
TimeSpan? refreshTokenLifetime = null)
|
|
{
|
|
if (service is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(service));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(subject))
|
|
{
|
|
throw new ArgumentException("Subject cannot be null or empty.", nameof(subject));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(issuer))
|
|
{
|
|
throw new ArgumentException("Issuer cannot be null or empty.", nameof(issuer));
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(audience))
|
|
{
|
|
throw new ArgumentException("Audience cannot be null or empty.", nameof(audience));
|
|
}
|
|
|
|
var accessLifetime = accessTokenLifetime ?? DefaultAccessTokenLifetime;
|
|
var refreshLifetime = refreshTokenLifetime ?? DefaultRefreshTokenLifetime;
|
|
|
|
var baseAccessDescriptor = TokenDescriptor.Create()
|
|
.ForSubject(subject)
|
|
.IssuedBy(issuer)
|
|
.ForAudience(audience)
|
|
.WithLifetime(accessLifetime);
|
|
|
|
configure?.Invoke(baseAccessDescriptor);
|
|
|
|
var accessToken = service.Issue(baseAccessDescriptor);
|
|
|
|
// Refresh token uses same claims (roles, custom claims) but longer lifetime
|
|
var baseRefreshDescriptor = TokenDescriptor.Create()
|
|
.ForSubject(subject)
|
|
.IssuedBy(issuer)
|
|
.ForAudience(audience)
|
|
.AsRefreshToken()
|
|
.WithLifetime(refreshLifetime);
|
|
|
|
configure?.Invoke(baseRefreshDescriptor);
|
|
|
|
var refreshToken = service.Issue(baseRefreshDescriptor);
|
|
|
|
return new AccessTokenRefreshTokenPair(accessToken, refreshToken);
|
|
}
|
|
}
|
|
}
|