Multiple Schemes
Combine several authentication mechanisms (Basic, API Key, JWT Bearer) in a single server and allow routes to accept one of several schemes. This pattern lets you:
- Gradually migrate between auth types (e.g., Basic -> JWT) without breaking clients.
- Support automation (API key) and interactive users (Basic) simultaneously.
- Offer short-lived bearer tokens while retaining a fallback credential.
When multiple schemes are specified in
-AuthorizationSchema, the request is authorized if ANY scheme succeeds.
Full source
File: pwsh/tutorial/examples/8.8-Multiple-Schemes.ps1
<#
Sample: Multiple Authentication Schemes
Purpose: Combine Basic, API Key, and JWT Bearer schemes in one server.
File: 8.8-Multiple-Schemes.ps1
Notes: Shows grouping routes and applying distinct schemes per endpoint.
#>
param(
[int]$Port = 5000,
[IPAddress]$IPAddress = [IPAddress]::Loopback
)
# 1. Logging
New-KrLogger | Add-KrSinkConsole | Register-KrLogger -Name 'console' -SetAsDefault | Out-Null
# 2. Server
New-KrServer -Name 'Auth Multi'
# 3. Listener
Add-KrEndpoint -Port $Port -IPAddress $IPAddress -SelfSignedCert
# 5. Basic auth scheme
Add-KrBasicAuthentication -Name 'BasicPS' -Realm 'Multi' -AllowInsecureHttp -ScriptBlock {
param($Username, $Password) # Plain text for tutorial simplicity
$Username -eq 'admin' -and $Password -eq 'password'
}
# 6. API key scheme
Add-KrApiKeyAuthentication -Name 'KeySimple' -AllowInsecureHttp -HeaderName 'X-Api-Key' -ExpectedKey 'my-secret-api-key'
# 7. JWT bearer scheme setup
$builder = New-KrJWTBuilder |
Add-KrJWTIssuer -Issuer 'KestrunApi' |
Add-KrJWTAudience -Audience 'KestrunClients' |
Protect-KrJWT -HexadecimalKey '6f1a1ce2e8cc4a5685ad0e1d1f0b8c092b6dce4f7a08b1c2d3e4f5a6b7c8d9e0' -Algorithm HS256
$res = Build-KrJWT -Builder $builder
$validation = $res | Get-KrJWTValidationParameter
Add-KrJWTBearerAuthentication -Name 'Bearer' -ValidationParameter $validation
# 8. Finalize configuration
Enable-KrConfiguration
# 9. Map routes with different schemes
Add-KrRouteGroup -Prefix '/secure' {
# Pure Basic route
Add-KrMapRoute -Verbs Get -Pattern '/basic' -AuthorizationSchema 'BasicPS' -ScriptBlock {
Write-KrTextResponse 'Basic OK'
}
# API Key OR Basic (either accepted: order matters only for challenge response)
Add-KrMapRoute -Verbs Get -Pattern '/key' -AuthorizationSchema 'KeySimple', 'BasicPS' -ScriptBlock {
Write-KrTextResponse 'Key OK'
}
# JWT OR Basic
Add-KrMapRoute -Verbs Get -Pattern '/jwt' -AuthorizationSchema 'Bearer', 'BasicPS' -ScriptBlock {
Write-KrTextResponse 'JWT OK'
}
# Issue a JWT using Basic (to demonstrate acquiring a token for /secure/jwt)
Add-KrMapRoute -Verbs Get -Pattern '/token/new' -AuthorizationSchema 'BasicPS' -ScriptBlock {
$user = $Context.User.Identity.Name
$build = Copy-KrJWTTokenBuilder -Builder $builder |
Add-KrJWTSubject -Subject $user |
Add-KrJWTClaim -UserClaimType Name -Value $user |
Add-KrJWTClaim -UserClaimType Role -Value 'admin' |
Build-KrJWT
$token = $build | Get-KrJWTToken
Write-KrJsonResponse @{ access_token = $token; expires = $build.Expires }
}
}
# 10. Start server
Start-KrServer -CloseLogsOnExit
Step-by-step
- Logging & server: configure a console logger and create the server.
- Listener: bind loopback HTTPS (self-signed cert for demo). Use
-SkipCertificateCheckin test clients. - Runtime: enable PowerShell runtime.
- Basic scheme: password-based initial authentication (
BasicPS). - API Key scheme: header-based key (
X-Api-Key) for automation (KeySimple). - JWT builder: configure issuer, audience, protection (HMAC), and build once for validation parameters.
- JWT bearer scheme: register bearer validation under name
Bearer. - Enable configuration: finalize server/auth registration.
-
Secure group: under
/securedefine routes with different accepted schemes:/secure/basic(Basic only)/secure/key(API Key OR Basic)/secure/jwt(JWT OR Basic)/secure/token/new(Basic only) issues a JWT for subsequent bearer access.
- Start server.
Multiple scheme semantics
Add-KrMapRoute -AuthorizationSchema 'KeySimple','BasicPS' means:
- Try API Key validation.
- If it fails (no key or invalid), attempt Basic.
- If all listed schemes fail, the route returns 401 with the challenge header(s) for the first failing scheme.
Order matters for which challenge header the client sees first; place your preferred or most common scheme first.
Try it
Assumptions: server listening on https://127.0.0.1:5000 with self-signed cert.
# Basic auth header
$basic = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('admin:password'))
# 1. Basic-only route
Invoke-RestMethod https://127.0.0.1:5000/secure/basic -SkipCertificateCheck -Headers @{ Authorization = $basic }
# 2. API Key OR Basic route (use API key)
Invoke-RestMethod https://127.0.0.1:5000/secure/key -SkipCertificateCheck -Headers @{ 'X-Api-Key' = 'my-secret-api-key' }
# 3. API Key OR Basic route (fallback to Basic)
Invoke-RestMethod https://127.0.0.1:5000/secure/key -SkipCertificateCheck -Headers @{ Authorization = $basic }
# 4. JWT OR Basic route (initially with Basic)
Invoke-RestMethod https://127.0.0.1:5000/secure/jwt -SkipCertificateCheck -Headers @{ Authorization = $basic }
# 5. Obtain JWT via token issuance (Basic required)
$token = (Invoke-RestMethod https://127.0.0.1:5000/secure/token/new -SkipCertificateCheck -Headers @{ Authorization = $basic }).access_token
# 6. Call JWT route with Bearer token
Invoke-RestMethod https://127.0.0.1:5000/secure/jwt -SkipCertificateCheck -Headers @{ Authorization = "Bearer $token" }
# 7. (Optional) Inspect token claims
Get-KrJWTInfo -Token $token | Format-List Issuer, Audience, Expires, Claims
If you supply both a valid API Key and Basic header, the first listed scheme that succeeds authorizes the request.
References
- Add-KrBasicAuthentication
- Add-KrApiKeyAuthentication
- New-KrJWTBuilder
- Add-KrJWTIssuer
- Add-KrJWTAudience
- Protect-KrJWT
- Build-KrJWT
- Get-KrJWTValidationParameter
- Add-KrJWTBearerAuthentication
- Add-KrMapRoute
- Add-KrRouteGroup
- Copy-KrJWTTokenBuilder
- Add-KrJWTSubject
- Add-KrJWTClaim
- Get-KrJWTInfo
- New-KrServer
- Add-KrEndpoint
- Enable-KrConfiguration
- Start-KrServer
Troubleshooting
| Symptom | Cause / Details | Fix / Guidance |
|---|---|---|
| 401 (Basic) | Wrong password / header missing | Recreate Authorization: Basic ... header |
| 401 (API Key) | Missing/incorrect X-Api-Key | Provide correct key value |
| 401 (Bearer) | Missing / expired / invalid JWT | Obtain fresh token via /secure/token/new |
| Wrong challenge header shown | Less preferred scheme listed first | Reorder names in -AuthorizationSchema |
| Claims missing in JWT | Builder reused without copy | Use Copy-KrJWTTokenBuilder before adding per-request claims |
| Token not expiring | No validity limiter configured | Add Limit-KrJWTValidity -Minutes <n> to builder |
| API Key exposed in logs | Verbose logging includes headers | Redact or lower log verbosity for headers |
Previous / Next
- Previous: Claims & Policies
- Next: Full Demo