AuthServer Configuration - Skills Guide¶
Skill Type: Authentication & Authorization Server Setup
Technology Stack: .NET, ABP Framework, OpenIddict, Hangfire, Valkey
Complexity Level: Advanced
Last Updated: 2025
📋 Overview¶
This guide covers configuring the AuthServer in a layered ABP application, including OpenIddict certificate management, Hangfire background jobs, Valkey/Redis caching, Docker secrets, and branding.
Configuration Areas¶
- ✅ OpenIddict certificate management
- ✅ Hangfire background job processing
- ✅ Valkey/Redis distributed caching & locking
- ✅ Docker secrets for containerized environments
- ✅ Branding and UI customization
- ✅ NPM package management
🔐 Step 1: Configure OpenIddict¶
- Add certificate handling methods for signing and encryption:
using GridLab.Abp.ZeroTrust;
private X509Certificate2 GetSigningCertificate(ServiceConfigurationContext context, IConfiguration configuration)
{
var certificateFactory = context.Services.GetCertificateFactory();
return certificateFactory.CreateFromPkcs12File(
configuration["App:Certificates:Signing:Path"],
configuration["App:Certificates:Signing:CertPassphrase"]
);
}
using GridLab.Abp.ZeroTrust;
private X509Certificate2 GetEncryptionCertificate(ServiceConfigurationContext context, IConfiguration configuration)
{
var certificateFactory = context.Services.GetCertificateFactory();
return certificateFactory.CreateFromPkcs12File(
configuration["App:Certificates:Encryption:Path"],
configuration["App:Certificates:Encryption:CertPassphrase"]
);
}
Add issuer resolution and access token lifetime methods:
private string GetIssuer(IConfiguration configuration)
{
// Solves bearer was not authenticated. Failure message: IDX10205: Issuer validation failed.
var issuer = configuration["App:Issuer"]?.Trim();
var selfUrl = configuration["App:SelfUrl"]?.Trim();
if (!string.IsNullOrEmpty(issuer))
return issuer;
if (!string.IsNullOrEmpty(selfUrl))
return selfUrl;
throw new InvalidOperationException("Neither App:Issuer nor App:SelfUrl is provided in configuration.");
}
private TimeSpan GetAccessTokenLifetime(IConfiguration configuration)
{
var accessTokenLifetimeString = configuration["App:AccessTokenLifetime"]?.Trim();
var defaultAccessTokenLifetime = TimeSpan.FromHours(1); // Define a default fallback value (1 hour)
if (TimeSpan.TryParse(accessTokenLifetimeString, out var accessTokenLifetime))
return accessTokenLifetime;
return defaultAccessTokenLifetime;
}
⏰ Step 2: Add Hangfire Support¶
- Install and configure
GridLab.Abp.Hangfiremodule:
Install the NuGet package:
<ItemGroup>
<PackageReference Include="GridLab.Abp.Hangfire" Version="X.Y.Z" />
</ItemGroup>
Replace version number with latest available.
Add the AbpHangfireModule to the dependency list of your module:
[DependsOn(
//...other dependencies
typeof(AbpGridLabHangfireModule)
)]
public class GmssAuthServerModule : AbpModule
{
}
Configure Hangfire module;
private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
{
string serverName = configuration["Hangfire:ServerName"] ?? "gmss-auth-server";
string schemaName = configuration["Hangfire:SchemaName"] ?? string.Empty;
string? connectionString = configuration.GetConnectionString(GmssDbProperties.ConnectionStringHangfireName);
if (connectionString.IsNullOrEmpty())
throw new ArgumentNullException(paramName: GmssDbProperties.ConnectionStringHangfireName);
var serverStorageOptions = new EFCoreStorageOptions
{
CountersAggregationInterval = new TimeSpan(0, 5, 0),
DistributedLockTimeout = new TimeSpan(0, 10, 0),
JobExpirationCheckInterval = new TimeSpan(0, 30, 0),
QueuePollInterval = new TimeSpan(0, 0, 15),
Schema = schemaName,
SlidingInvisibilityTimeout = new TimeSpan(0, 5, 0),
};
var options = new DbContextOptionsBuilder<GmssHangfireDbContext>()
.UseSqlServer(connectionString)
.Options;
GlobalConfiguration.Configuration.UseEFCoreStorage(
() => new GmssHangfireDbContext(options, schemaName),
serverStorageOptions
);
Configure<AbpHangfireOptions>(options =>
{
options.Storage = EFCoreStorage.Current;
options.ServerOptions = new BackgroundJobServerOptions() { ServerName = serverName };
});
}
Add a new section for hangfire to application configuration settings.
"Hangfire": {
"ServerName": "gmss-auth-server"
},
Make sure you have updated your connection strings defined under ConnectionString at appsettings.json file.
"ConnectionStrings": {
...
"Hangfire": "Server=(localdb)\\MSSQLLocalDB;Database=gmss-hangfire;Trusted_Connection=True;TrustServerCertificate=true"
}
📦 Step 3: Configure Caching with Valkey¶
- Replace
Volo.Abp.Caching.StackExchangeRediswithGridLab.Abp.Caching.StackExchangeValkey.
Remove:
<ItemGroup>
<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="X.Y.Z" />
</ItemGroup>
Add:
<ItemGroup>
<PackageReference Include="GridLab.Abp.Caching.StackExchangeValkey" Version="X.Y.Z" />
</ItemGroup>
Replace package at GmssAuthServerModule.cs
[DependsOn(
typeof(GmssEntityFrameworkCoreModule)
//...other dependencies
~~typeof(AbpStackExchangeRedisModule),~~
typeof(AbpGridLabStackExchangeValkeyModule),
//...other dependencies
)]
public class GmssAuthServerModule : AbpModule
{
}
Add ConfigureCache, ConfigureDataProtection and ConfigureDistributedLocking methods for handling cache connections.
private void ConfigureCache(IConfiguration configuration)
{
Configure<AbpDistributedCacheOptions>(options =>
{
options.KeyPrefix = "Gmss:";
});
}
Use AbpValkeyConfigurationHelper.CreateConfigurationOptions helper method to create connection.
private void ConfigureDataProtection(
ServiceConfigurationContext context,
IConfiguration configuration,
IWebHostEnvironment hostingEnvironment)
{
if (AbpStudioAnalyzeHelper.IsInAnalyzeMode)
{
return;
}
var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("Gmss");
if (!hostingEnvironment.IsDevelopment())
{
var certificateProvider = context.Services.GetCertificateProvider();
var certificateValidator = context.Services.GetCertificateValidator();
var options = configuration
.GetSection(AbpGridLabValkeyOptions.ServiceName)
.Get<AbpGridLabValkeyOptions>() ?? new AbpGridLabValkeyOptions();
var configurationOptions = AbpValkeyConfigurationHelper.CreateConfigurationOptions(
serviceOptions: options,
certificateProvider: certificateProvider,
certificateValidator: certificateValidator
);
var redis = ConnectionMultiplexer.Connect(configurationOptions);
dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "Gmss-Protection-Keys");
}
}
private void ConfigureDistributedLocking(ServiceConfigurationContext context, IConfiguration configuration)
{
if (AbpStudioAnalyzeHelper.IsInAnalyzeMode)
{
return;
}
context.Services.AddSingleton<IDistributedLockProvider>(sp =>
{
var certificateProvider = context.Services.GetCertificateProvider();
var certificateValidator = context.Services.GetCertificateValidator();
var options = configuration
.GetSection(AbpGridLabValkeyOptions.ServiceName)
.Get<AbpGridLabValkeyOptions>() ?? new AbpGridLabValkeyOptions();
var configurationOptions = AbpValkeyConfigurationHelper.CreateConfigurationOptions(
serviceOptions: options,
certificateProvider: certificateProvider,
certificateValidator: certificateValidator
);
var connection = ConnectionMultiplexer.Connect(configurationOptions);
return new RedisDistributedSynchronizationProvider(connection.GetDatabase());
});
}
Add a new section for caching to application configuration settings.
"Valkey": {
"Enabled": true,
"Configuration": {
"Configuration": "127.0.0.1"
},
"Ssl": {
"Enabled": false
},
"Connection": {
"ConnectTimeout": 30,
"ConnectRetry": 3
}
},
⏱️ Step 4: Activate UTC Clock¶
- Set clock to UTC at
GmssAuthServerModule.cs:
private void ConfigureClock()
{
Configure<AbpClockOptions>(options =>
{
options.Kind = DateTimeKind.Utc;
});
}
🔐 Step 5: Install Docker Secrets Provider¶
- Install
GridLab.Abp.Security.Providers.DockerSecretsmodule:
<ItemGroup>
<PackageReference Include="GridLab.Abp.Security.Providers.DockerSecrets" Version="X.Y.Z" />
</ItemGroup>
Replace version number with latest available.
Extend dependency list at GmssAuthServerModule.cs
[DependsOn(
typeof(GmssEntityFrameworkCoreModule)
//
typeof(AbpGridLabSecurityProvidersDockerSecretsModule),
//...other dependencies
)]
public class GmssAuthServerModule : AbpModule
{
}
.NET Core configuration provider extensions allows reading docker secrets files and pull them into the .net core configuration.
builder.Host
.UseAutofac()
....
.UseSerilog((context, services, loggerConfiguration) =>
{
loggerConfiguration
#if DEBUG
.MinimumLevel.Debug()
.MinimumLevel.Override("DockerSecrets", LogEventLevel.Debug)
#else
.MinimumLevel.Information()
.MinimumLevel.Override("DockerSecrets", LogEventLevel.Information)
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
.WriteTo.Async(c => c.Console())
})
....
.ConfigureAppConfiguration((context, config) => {
if (!context.HostingEnvironment.IsDevelopment())
{
config.AddDockerSecrets(
options: () => new DockerSecretsConfigurationOptions
{
ColonPlaceholder = "__"
},
secretsPath: "/run/secrets",
logger: new SerilogLoggerProvider(Log.Logger).CreateLogger("DockerSecrets")
);
}
})
....
🎨 Step 6: Add Branding Provider¶
- Add
GmssBrandingProvider.csto theGridLab.Gmss.AuthServerproject root.
Copy the wwwroot folder from the template to src/GridLab.Gmss.AuthServer. Delete existing logo-dark.png and logo-light.png logos.
[Dependency(ReplaceServices = true)]
public class GmssBrandingProvider : DefaultBrandingProvider
{
private IStringLocalizer<GmssResource> _localizer;
public GmssBrandingProvider(IStringLocalizer<GmssResource> localizer)
{
_localizer = localizer;
}
public override string AppName => "Gmss Auth Server";
public override string LogoUrl => "/images/logo/leptonx/logo-light.png";
public override string LogoReverseUrl => "/images/logo/leptonx/logo-dark.png";
}
📦 Step 7: Configure NPM Packages¶
- Verify and update npm package versions in
package.json:
{
"version": "0.1.0",
"name": "my-app-authserver",
"private": true,
"dependencies": {
"@volo/abp.aspnetcore.mvc.ui.theme.lepton": "~X.Y.Z",
"@volo/account": "~X.Y.Z"
}
}
add extra packages if necessary
{
"version": "0.1.0",
"name": "my-app-authserver",
"private": true,
"dependencies": {
"@volo/abp.aspnetcore.mvc.ui.theme.lepton": "~X.Y.Z",
"@volo/account": "~X.Y.Z",
"ace-builds": "^1.4.12",
"bootstrap-markdown-editor-4": "^3.0.0",
"jquery-datetimepicker": "^2.5.21",
"moment": "^2.29.1"
}
}
In order to run abp install-libs automation scripts, a definition must be made in abp.resourcemapping.js.
module.exports = {
aliases: {
"@node_modules": "./node_modules",
"@libs": "./wwwroot/libs"
},
clean: [
"@libs",
"!@libs/**/foo.txt"
],
mappings: {
"@node_modules/bootstrap-markdown-editor-4/dist/css/bootstrap-markdown-editor.min.css": "@libs/bootstrap-markdown-editor-4/css/",
"@node_modules/bootstrap-markdown-editor-4/dist/js/bootstrap-markdown-editor.min.js": "@libs/bootstrap-markdown-editor-4/js/",
"@node_modules/ace-builds/src-noconflict/ace.js": "@libs/ace/",
"@node_modules/ace-builds/src-noconflict/theme-tomorrow.js": "@libs/ace/",
"@node_modules/ace-builds/src-noconflict/mode-markdown.js": "@libs/ace/",
"@node_modules/ace-builds/src-noconflict/ext-language_tools.js": "@libs/ace/",
"@node_modules/moment/moment.js": "@libs/moment/",
"@node_modules/jquery-datetimepicker/jquery.datetimepicker.js": "@libs/jquery-datetimepicker/",
"@node_modules/jquery-datetimepicker/jquery.datetimepicker.css": "@libs/jquery-datetimepicker/",
}
};
Clean wwwroot/libs and node_modules folders for clean installation. This is required action for abp install-libs script.
ABP CLI's abp install-libs command copies resources from node_modules to wwwroot/libs folder. Each standard package (see the @ABP NPM Packages section) defines the mapping for its own files. So, most of the time, you only configure dependencies.
🔧 Step 8: Enable PDB Loading¶
- Add MSBuild instruction to
GridLab.Gmss.AuthServer.csprojfor.pdbfile loading during debugging:
<Target Name="_ResolveCopyLocalNuGetPackagePdbs" Condition="$(CopyLocalLockFileAssemblies) == true" AfterTargets="ResolveReferences">
<ItemGroup>
<ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->'%(RootDir)%(Directory)%(Filename).pdb')" Condition="'%(ReferenceCopyLocalPaths.NuGetPackageId)' != '' and Exists('%(RootDir)%(Directory)%(Filename).pdb')" />
</ItemGroup>
</Target>