JWT Tokens
Issue and validate bearer tokens for stateless auth.
Full source
File: pwsh/tutorial/examples/8.4-Jwt.ps1
<#
Sample: JWT Token Issuance & Validation
Purpose: Show issuing a JWT after Basic auth and protecting routes with a bearer scheme.
File: 8.4-Jwt.ps1
Notes: Uses symmetric HMAC key; store securely (e.g., KeyVault, env var) in production.
#>
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 JWT'
# 3. Listener
Add-KrEndpoint -Port $Port -IPAddress $IPAddress -SelfSignedCert
# 5. Initial Basic scheme for token issuance
Add-KrBasicAuthentication -Name 'BasicInit' -Realm 'Init' -AllowInsecureHttp -ScriptBlock { param($Username, $Password) $Username -eq 'admin' -and $Password -eq 'password' }
# 6. Build JWT configuration
$jwtBuilder = New-KrJWTBuilder |
Add-KrJWTIssuer -Issuer 'KestrunApi' |
Add-KrJWTAudience -Audience 'KestrunClients' |
Protect-KrJWT -HexadecimalKey '6f1a1ce2e8cc4a5685ad0e1d1f0b8c092b6dce4f7a08b1c2d3e4f5a6b7c8d9e0' -Algorithm HS256
$result = Build-KrJWT -Builder $jwtBuilder
$validation = $result | Get-KrJWTValidationParameter
# 7. Register bearer scheme
Add-KrJWTBearerAuthentication -Name 'Bearer' -ValidationParameter $validation
# 8. Finalize configuration
Enable-KrConfiguration
# 9. Route: issue token (requires Basic)
Add-KrMapRoute -Verbs Get -Pattern '/token/new' -AuthorizationSchema 'BasicInit' -ScriptBlock {
$user = $Context.User.Identity.Name
Write-KrLog -Level Information -Message 'Generating JWT token for user {User}' -Values $user
Write-KrLog -Level Information -Message 'Issuer : {Issuer} ' -Values $JwtTokenBuilder.Issuer
Write-KrLog -Level Information -Message 'Audience : {Audience} ' -Values $JwtTokenBuilder.Audience
Write-KrLog -Level Information -Message 'Algorithm: {Algorithm} ' -Values $JwtTokenBuilder.Algorithm
$build = Copy-KrJWTTokenBuilder -Builder $jwtBuilder |
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 }
}
Add-KrMapRoute -Verbs Get -Pattern '/token/renew' -AuthorizationSchema $JwtScheme -ScriptBlock {
$user = $Context.User.Identity.Name
Write-KrLog -Level Information -Message 'Generating JWT token for user {0}' -Values $user
$accessToken = $jwtBuilder | Update-KrJWT -FromContext
Write-KrJsonResponse -InputObject @{
access_token = $accessToken
token_type = 'Bearer'
expires_in = $build.Expires
} -ContentType 'application/json'
}
# 10. Route: protected with bearer token
Add-KrMapRoute -Verbs Get -Pattern '/secure/jwt/hello' -AuthorizationSchema 'Bearer' -ScriptBlock {
Write-KrTextResponse -InputObject "JWT Hello $( $Context.User.Identity.Name )"
}
# 11. Start server
Start-KrServer -CloseLogsOnExit
Step-by-step
- Logger: create console logger and register as default.
- Server: create named server
Auth JWT. - Listener: add HTTPS listener on loopback with a self-signed certificate.
- Runtime: enable PowerShell runtime.
- Initial auth: add Basic scheme (
BasicInit) for first-factor credential check. - Build JWT config: chain issuer, audience, and HMAC protection; build & capture validation params.
- Bearer scheme: register JWT bearer authentication using validation parameters.
- Enable configuration: lock in server/listener/auth schemes.
- Issue route:
/token/newrequires Basic and creates a token with subject, name and role claims. - Renew route:
/token/renew(Bearer) refreshes the token usingUpdate-KrJWT -FromContext. - Protected route:
/secure/jwt/hellorequires a valid bearer token. - Start server.
Try it
# 1. Get initial bearer token using Basic auth
$basicHeader = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes('admin:password'))
$token = (Invoke-RestMethod https://127.0.0.1:5000/token/new -SkipCertificateCheck -Headers @{ Authorization = $basicHeader }).access_token
# 2. Call protected route with current bearer token
Invoke-RestMethod https://127.0.0.1:5000/secure/jwt/hello -SkipCertificateCheck -Headers @{ Authorization = "Bearer $token" }
# 3. Renew token (issue a fresh one using existing bearer)
$refreshedToken = (Invoke-RestMethod -Uri https://localhost:5000/token/renew -SkipCertificateCheck -Headers @{ Authorization = "Bearer $token" }).access_token
# 4. Call protected route with renewed token
Invoke-RestMethod -Uri https://localhost:5000/secure/jwt/hello -SkipCertificateCheck -Headers @{ Authorization = "Bearer $refreshedToken" }
# (Optional) Inspect token (header + payload; signature not revalidated here):
Get-KrJWTInfo -Token $token | Format-List Issuer, Audience, Expires, Algorithm, Claims
# (Optional) Validate token explicitly (signature + lifetime):
Test-KrJWT -Token $token -ValidationParameter ($jwtBuilder | Build-KrJWT | Get-KrJWTValidationParameter)
Note:
-SkipCertificateCheckis used because the sample listener issues a self-signed cert. For production, trust the certificate or use a CA-issued cert instead.
References
- New-KrJWTBuilder
- Add-KrJWTIssuer
- Add-KrJWTAudience
- Protect-KrJWT
- Build-KrJWT
- Copy-KrJWTTokenBuilder
- Add-KrJWTSubject
- Add-KrJWTClaim
- Update-KrJWT
- Get-KrJWTValidationParameter
- Add-KrJWTBearerAuthentication
- Add-KrBasicAuthentication
- Add-KrMapRoute
- Start-KrServer
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 401 Bearer | Missing or invalid token | Provide Authorization: Bearer <token> |
| Token expired | Expiration passed | Re-issue via /token/new or /token/renew |
| Signature invalid | Wrong key/algorithm | Ensure same secret & algorithm |
| 401 on renew | Missing/invalid bearer on renew | Include current valid Authorization: Bearer |
| Claims not updated | Builder not copied per request | Use Copy-KrJWTTokenBuilder before adding claims |
Previous / Next
See also: JWT Guide for advanced scenarios (copying builders, renewal patterns, security).