Skip to content

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.xml to 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