ASP.NET Core JWT Identity Auth
JWTs enable stateless authentication of clients without servers needing to maintain any Auth state in server infrastructure or perform any I/O to validate a token. As such, JWTs are a popular choice for Microservices as they only need to configured with confidential keys to validate access.
ASP.NET Core JWT Authentication​
ServiceStack's JWT Identity Auth reimplements many of the existing ServiceStack JWT AuthProvider
features but instead of its own implementation, integrates with and utilizes ASP.NET Core's built-in JWT Authentication that's
configurable in .NET Apps with the .AddJwtBearer()
extension method, e.g:
Program.cs​
services.AddAuthentication()
.AddJwtBearer(options => {
options.TokenValidationParameters = new()
{
ValidIssuer = config["JwtBearer:ValidIssuer"],
ValidAudience = config["JwtBearer:ValidAudience"],
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(config["JwtBearer:IssuerSigningKey"]!)),
ValidateIssuerSigningKey = true,
};
})
.AddIdentityCookies(options => options.DisableRedirectsForApis());
Then use the JwtAuth()
method to enable and configure ServiceStack's support for ASP.NET Core JWT Identity Auth:
Configure.Auth.cs​
public class ConfigureAuth : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services => {
services.AddPlugin(new AuthFeature(IdentityAuth.For<ApplicationUser>(
options => {
options.SessionFactory = () => new CustomUserSession();
options.CredentialsAuth();
options.JwtAuth(x => {
// Enable JWT Auth Features...
});
})));
});
}
Enable in Swagger UI​
Once configured we can enable JWT Auth in Swagger UI by installing Swashbuckle.AspNetCore:
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
Then enable Open API, Swagger UI, ServiceStack's support for Swagger UI and the JWT Bearer Auth option:
public class ConfigureOpenApi : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices((context, services) => {
if (context.HostingEnvironment.IsDevelopment())
{
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
services.AddServiceStackSwagger();
services.AddJwtAuth();
//services.AddBasicAuth<Data.ApplicationUser>();
services.AddTransient<IStartupFilter,StartupFilter>();
}
});
public class StartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
=> app => {
// Provided by Swashbuckle library
app.UseSwagger();
app.UseSwaggerUI();
next(app);
};
}
}
This will enable the Authorize button in Swagger UI where you can authenticate with a JWT Token:
JWT Auth in Built-in UIs​
This also enables the JWT Auth Option in ServiceStack's built-in API Explorer, Locode and Admin UIs:
Authenticating with JWT​
JWT Identity Auth is a drop-in replacement for ServiceStack's JWT AuthProvider where Authenticating via Credentials
will convert the Authenticated User into a JWT Bearer Token returned in the HttpOnly, Secure ss-tok
Cookie
that will be used to Authenticate the client:
var client = new JsonApiClient(BaseUrl);
await client.SendAsync(new Authenticate {
provider = "credentials",
UserName = Username,
Password = Password,
});
var bearerToken = client.GetTokenCookie(); // ss-tok Cookie
JWT Refresh Tokens​
Refresh Tokens can be used to allow users to request a new JWT Access Token when the current one expires.
To enable support for JWT Refresh Tokens your IdentityUser
model should implement the IRequireRefreshToken
interface
which will be used to store the 64 byte Base64 URL-safe RefreshToken
and its RefreshTokenExpiry
in its persisted properties:
public class ApplicationUser : IdentityUser, IRequireRefreshToken
{
public string? RefreshToken { get; set; }
public DateTime? RefreshTokenExpiry { get; set; }
}
Now after successful authentication, the RefreshToken
will also be returned in the ss-reftok
Cookie:
var refreshToken = client.GetRefreshTokenCookie(); // ss-reftok Cookie
Transparent Server Auto Refresh of JWT Tokens​
To be able to terminate a users access, Users need to revalidate their eligibility to verify they're still allowed access (e.g. deny Locked out users). This JWT revalidation pattern is implemented using Refresh Tokens which are used to request revalidation of their access and reissuing a new JWT Access Token which can be used to make authenticated requests until it expires.
As Cookies are used to return Bearer and Refresh Tokens ServiceStack is able to implement the revalidation logic on the server where it transparently validates Refresh Tokens, and if a User is eligible will reissue a new JWT Token Cookie that replaces the expired Access Token Cookie.
Thanks to this behavior HTTP Clients will be able to Authenticate with just the Refresh Token, which will transparently reissue a new JWT Access Token Cookie and then continue to perform the Authenticated Request:
var client = new JsonApiClient(BaseUrl);
client.SetRefreshTokenCookie(RefreshToken);
var response = await client.SendAsync(new Secured { ... });
There's also opt-in sliding support for extending a User's RefreshToken after usage which allows Users to treat their Refresh Token like an API Key where it will continue extending whilst they're continuously using it to make API requests, otherwise expires if they stop. How long to extend the expiry of Refresh Tokens after usage can be configured with:
options.JwtAuth(x => {
// How long to extend the expiry of Refresh Tokens after usage (default None)
x.ExtendRefreshTokenExpiryAfterUsage = TimeSpan.FromDays(90);
});
Convert Session to Token Service​
Another useful Service that's available is being able to Convert your current Authenticated Session into a Token
with the ConvertSessionToToken
Service which can be enabled with:
options.JwtAuth(x => {
x.IncludeConvertSessionToTokenService = true;
});
This can be useful for when you want to Authenticate via an external OAuth Provider that you then want to convert into a stateless
JWT Token by calling the ConvertSessionToToken
on the client, e.g:
.NET Clients​
await client.SendAsync(new ConvertSessionToToken());
TypeScript/JavaScript​
fetch('/session-to-token', { method:'POST', credentials:'include' })
The default behavior of ConvertSessionToToken
is to remove the Current Session from the Auth Server which will prevent
access to protected Services using our previously Authenticated Session. If you still want to preserve your existing Session
you can indicate this with:
await client.SendAsync(new ConvertSessionToToken {
PreserveSession = true
});
JWT Options​
Other configuration options available for Identity JWT Auth include:
options.JwtAuth(x => {
// How long should JWT Tokens be valid for. (default 14 days)
x.ExpireTokensIn = TimeSpan.FromDays(14);
// How long should JWT Refresh Tokens be valid for. (default 90 days)
x.ExpireRefreshTokensIn = TimeSpan.FromDays(90);
x.OnTokenCreated = (req, user, claims) => {
// Customize which claims are included in the JWT Token
};
// Whether to invalidate Refresh Tokens on Logout (default true)
x.InvalidateRefreshTokenOnLogout = true;
// How long to extend the expiry of Refresh Tokens after usage (default None)
x.ExtendRefreshTokenExpiryAfterUsage = null;
});