Authentication & JWT Setup¶
How It Works¶
Client → POST /api/tokens → Authorization Server → JWT (signed with private RSA key)
Client → GET /api/jobs/… → Web API → validates JWT with public RSA key
- The Authorization Server holds the private key and issues signed JWTs.
- All Web API projects hold only the public key and validate incoming tokens.
- The keys are RSA XML strings — store them as environment variables, never hardcode them.
Step 1 — Generate an RSA Key Pair¶
Run the following PowerShell snippet once per environment. It outputs two XML files — one with both keys (private), one with only the public key:
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider -ArgumentList 2048
$rsa.ToXmlString($true) | Out-File key.private.xml # keep secret — Auth Server only
$rsa.ToXmlString($false) | Out-File key.public.xml # safe to share with Web API projects
Warning: Never commit
key.private.xmlto source control. Treat it like a password.
Step 2 — Store Keys as Environment Variables¶
In each environment (dev machine, CI/CD, Azure App Service), set:
PrivateRSAKey = <content of key.private.xml> # Auth Server only
PublicRSAKey = <content of key.public.xml> # All Web API projects
Reference them in appsettings.json using [env:] placeholders:
{
"Tokens": {
"Issuer": "dhigroup.com",
"Audience": "dhigroup.com",
"PublicRSAKey": "[env:PublicRSAKey]",
"ExpirationInMinutes": 30,
"RefreshExpirationInDays": 365
}
}
The [env:...] placeholder is resolved at runtime by string.Resolve() — see Web API Core for details.
Step 3 — Configure JWT Validation in Your Web API¶
In Program.cs, use RSA.BuildSigningKey (from DHI.Services.WebApiCore) to parse the public key XML:
using DHI.Services.WebApiCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration["Tokens:Issuer"],
ValidAudience = builder.Configuration["Tokens:Audience"],
IssuerSigningKey = RSA.BuildSigningKey(
builder.Configuration["Tokens:PublicRSAKey"].Resolve())
};
});
builder.Services.AddAuthorization();
In the pipeline (after app.UseRouting()):
app.UseAuthentication();
app.UseAuthorization();
Step 4 — Set Up an Authorization Server¶
The DHI Domain Services Authorization Server project template issues tokens. Install the Visual Studio extension (Extensions → Manage Extensions, search for "DHI") and create a new project from the DHI Domain Services Authorization Server template.
The Authorization Server exposes:
POST /api/tokens
Request body:
{
"username": "admin",
"password": "yourpassword"
}
Response:
{
"accessToken": "eyJ...",
"expiresIn": 1800,
"refreshToken": "..."
}
The Authorization Server needs the private RSA key in its configuration:
{
"Tokens": {
"Issuer": "dhigroup.com",
"Audience": "dhigroup.com",
"PrivateRSAKey": "[env:PrivateRSAKey]",
"PublicRSAKey": "[env:PublicRSAKey]",
"ExpirationInMinutes": 30,
"RefreshExpirationInDays": 365
}
}
Step 5 — Call a Protected Endpoint¶
Include the token in the Authorization header of every request:
Authorization: Bearer eyJ...
In Swagger UI, click Authorize (top right), enter Bearer <your-token>, and all subsequent calls will include the header automatically.
Authorization Policies (Optional)¶
You can add named policies to restrict endpoints to specific user roles or groups:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdministratorsOnly",
policy => policy.RequireClaim(ClaimTypes.GroupSid, "Administrators"));
options.AddPolicy("EditorsOnly",
policy => policy.RequireClaim(ClaimTypes.GroupSid, "Editors"));
});
Apply a policy to a controller or action:
[Authorize(Policy = "AdministratorsOnly")]
[HttpDelete("{id}")]
public IActionResult Delete(string connectionId, Guid id) { ... }
Dev-Only: Bypass Authentication Entirely¶
For local testing without a running Authorization Server, register AllowAnonymousHandler:
builder.Services.AddSingleton<IAuthorizationHandler, AllowAnonymousHandler>();
Warning: Remove this before any production deployment. It disables all authorization checks.
CORS (Cross-Origin Requests)¶
If a browser-based frontend calls your API from a different origin, add CORS in Program.cs:
builder.Services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.SetIsOriginAllowed(origin =>
new Uri(origin).Host == "localhost" ||
new Uri(origin).Host == "127.0.0.1")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
// In the pipeline:
app.UseCors("default");
Tighten the origin list before deploying to production.
Next: Deploying to Production