OAuth 2.0
OAuth 2.0 is a framework for delegated authorization: users grant apps access to resources without sharing passwords. For user sign‑in via third‑party providers (e.g., GitHub), use Authorization Code with PKCE and store resulting tokens in a cookie‑backed session.
OAuth 2.0 handles access; you still need a local session to persist the login in your app.
1. Core Concepts & Components
| Concept/Role | Description |
|---|---|
| Authorization Server | Issues codes and tokens; handles user authorization |
| Client (Your App) | The application requesting access on behalf of the user |
| Resource Owner | The user |
| Resource Server | API holding protected resources |
| Authorization Code | One‑time code exchanged for tokens |
| PKCE | Protects public clients during code exchange |
| Scope | Access permissions requested (e.g., read:user) |
| Redirect URI | Callback registered in the provider (e.g., /signin-oauth) |
| Policy Scheme | Kestrun scheme that authenticates via cookie and forwards challenges to the provider |
1.2 Choosing OAuth2 vs OIDC
When to choose which?
- Use OIDC when you need identity (ID Token, standard claims like
sub,name).- Use OAuth 2.0 when you only need delegated API access and plan to enrich identity from a provider API.
- Many modern providers (Azure AD, Google) are OIDC-capable; prefer OIDC for predictable identity semantics.
- Kestrun supports both; policy schemes keep your routes the same regardless of the backing provider.
1.1 Authorization Code + PKCE (flow)
sequenceDiagram
autonumber
participant B as Browser
participant A as App (Kestrun)
participant AS as Authorization Server (GitHub)
participant RS as Resource Server (API)
B->>A: GET /github/me (protected)
A-->>B: 302 to AS /authorize (client_id, redirect_uri, scope, code_challenge)
B->>AS: Login + consent
AS-->>B: 302 back to redirect_uri with code
B->>A: GET /signin-oauth?code=...
A->>AS: POST /token (code + code_verifier)
AS-->>A: access_token (+ id_token if OIDC provider)
A->>A: Create auth cookie (session)
A-->>B: 302 to original URL
B->>A: GET /github/me (with cookie)
A-->>B: 200 JSON claims/profile
A->>RS: (optional) GET /user or /userinfo (bearer access_token)
RS-->>A: profile/enrichment
2. Quick start (Generic OAuth 2.0)
Use the generic OAuth 2.0 cmdlet to configure any provider (example below uses GitHub endpoints):
# Configure your OAuth App first; set env vars for secrets
$env:GITHUB_CLIENT_ID = 'your-client-id'
$env:GITHUB_CLIENT_SECRET = 'your-client-secret'
New-KrServer -Name 'OAuth2 Demo'
Add-KrEndpoint -Port 5000 -IPAddress ([IPAddress]::Loopback) -SelfSignedCert
# Build generic OAuth 2.0 options
$options = [Kestrun.Authentication.OAuth2Options]::new()
$options.ClientId = $env:GITHUB_CLIENT_ID
$options.ClientSecret = $env:GITHUB_CLIENT_SECRET
$options.CallbackPath = '/signin-oauth'
$options.UsePkce = $true
$options.SaveTokens = $true
$options.AuthorizationEndpoint = 'https://github.com/login/oauth/authorize'
$options.TokenEndpoint = 'https://github.com/login/oauth/access_token'
$options.UserInformationEndpoint = 'https://api.github.com/user'
$options.Scope.Clear(); $options.Scope.Add('read:user'); $options.Scope.Add('user:email') | Out-Null
# Register OAuth2 (adds 'GitHub', 'GitHub.Cookies', 'GitHub.Policy')
Add-KrOAuth2Authentication -AuthenticationScheme 'GitHub' -Options $options
Enable-KrConfiguration
# Public landing (optional)
Add-KrHtmlTemplateRoute -Pattern '/' -HtmlTemplatePath './Assets/wwwroot/github/github-auth.html'
# Protected routes (policy scheme)
Add-KrMapRoute -Verbs Get -Pattern '/github/login' -AuthorizationScheme 'GitHub' -ScriptBlock {
Write-KrTextResponse 'Welcome via OAuth2 (GitHub)'
}
Add-KrMapRoute -Verbs Get -Pattern '/github/me' -AuthorizationScheme 'GitHub' -ScriptBlock {
$claims = foreach ($c in $Context.User.Claims) { @{ Type = $c.Type; Value = $c.Value } }
Write-KrJsonResponse @{ authenticated = $true; claims = $claims }
}
Add-KrMapRoute -Verbs Get -Pattern '/github/logout' -ScriptBlock {
Invoke-KrCookieSignOut -AuthKind 'OAuth2' -Scheme 'GitHub' -RedirectUri '/'
}
Start-KrServer -CloseLogsOnExit
Note: For GitHub, a convenience wrapper
Add-KrGitHubAuthenticationpreconfigures endpoints, PKCE, default scopes, optional email enrichment, and normalizes common claims (maps GitHub user and emails into standard claims). PreferAdd-KrOAuth2Authenticationfor portability across providers.
2.1 Quick start (Google example)
# Google OAuth 2.0 (OIDC-compatible) using generic options
$env:GOOGLE_CLIENT_ID = 'your-client-id'
$env:GOOGLE_CLIENT_SECRET = 'your-client-secret'
New-KrServer -Name 'Google OAuth'
Add-KrEndpoint -Port 5000 -IPAddress ([IPAddress]::Loopback) -SelfSignedCert
$opt = [Kestrun.Authentication.OAuth2Options]::new()
$opt.ClientId = $env:GOOGLE_CLIENT_ID
$opt.ClientSecret = $env:GOOGLE_CLIENT_SECRET
$opt.CallbackPath = '/signin-oauth'
$opt.UsePkce = $true
$opt.SaveTokens = $true
$opt.AuthorizationEndpoint = 'https://accounts.google.com/o/oauth2/v2/auth'
$opt.TokenEndpoint = 'https://oauth2.googleapis.com/token'
$opt.UserInformationEndpoint = 'https://openidconnect.googleapis.com/v1/userinfo'
$opt.Scope.Clear(); $null = $opt.Scope.Add('openid'); $null = $opt.Scope.Add('email'); $null = $opt.Scope.Add('profile')
Add-KrOAuth2Authentication -AuthenticationScheme 'Google' -Options $opt
Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/google/me' -AuthorizationScheme 'Google' -ScriptBlock {
$claims = foreach ($c in $Context.User.Claims) { @{ Type = $c.Type; Value = $c.Value } }
Write-KrJsonResponse @{ provider = 'google'; authenticated = $true; claims = $claims }
}
Start-KrServer -CloseLogsOnExit
2.2 Quick start (GitLab example)
# GitLab OAuth 2.0 using generic options
$env:GITLAB_CLIENT_ID = 'your-client-id'
$env:GITLAB_CLIENT_SECRET = 'your-client-secret'
New-KrServer -Name 'GitLab OAuth'
Add-KrEndpoint -Port 5000 -IPAddress ([IPAddress]::Loopback) -SelfSignedCert
$gitlab = [Kestrun.Authentication.OAuth2Options]::new()
$gitlab.ClientId = $env:GITLAB_CLIENT_ID
$gitlab.ClientSecret = $env:GITLAB_CLIENT_SECRET
$gitlab.CallbackPath = '/signin-oauth'
$gitlab.UsePkce = $true
$gitlab.SaveTokens = $true
$gitlab.AuthorizationEndpoint = 'https://gitlab.com/oauth/authorize'
$gitlab.TokenEndpoint = 'https://gitlab.com/oauth/token'
$gitlab.UserInformationEndpoint = 'https://gitlab.com/api/v4/user'
$gitlab.Scope.Clear(); $null = $gitlab.Scope.Add('read_user')
Add-KrOAuth2Authentication -AuthenticationScheme 'GitLab' -Options $gitlab
Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/gitlab/me' -AuthorizationScheme 'GitLab' -ScriptBlock {
$claims = foreach ($c in $Context.User.Claims) { @{ Type = $c.Type; Value = $c.Value } }
Write-KrJsonResponse @{ provider = 'gitlab'; authenticated = $true; claims = $claims }
}
Start-KrServer -CloseLogsOnExit
2.3 Quick start (Azure AD example)
# Azure AD (Microsoft Identity Platform) using generic OAuth 2.0 options
$tenant = 'common' # or your tenant ID (e.g., 'contoso.onmicrosoft.com' or GUID)
$env:AZURE_AD_CLIENT_ID = 'your-client-id'
$env:AZURE_AD_CLIENT_SECRET = 'your-client-secret'
New-KrServer -Name 'Azure AD OAuth'
Add-KrEndpoint -Port 5000 -IPAddress ([IPAddress]::Loopback) -SelfSignedCert
$aad = [Kestrun.Authentication.OAuth2Options]::new()
$aad.ClientId = $env:AZURE_AD_CLIENT_ID
$aad.ClientSecret = $env:AZURE_AD_CLIENT_SECRET
$aad.CallbackPath = '/signin-oauth'
$aad.UsePkce = $true
$aad.SaveTokens = $true
$aad.AuthorizationEndpoint = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/authorize"
$aad.TokenEndpoint = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token"
$aad.UserInformationEndpoint = 'https://graph.microsoft.com/v1.0/me'
$aad.Scope.Clear(); $null = $aad.Scope.Add('User.Read'); $null = $aad.Scope.Add('offline_access')
Add-KrOAuth2Authentication -AuthenticationScheme 'AzureAD' -Options $aad
Enable-KrConfiguration
Add-KrMapRoute -Verbs Get -Pattern '/azuread/me' -AuthorizationScheme 'AzureAD' -ScriptBlock {
$claims = foreach ($c in $Context.User.Claims) { @{ Type = $c.Type; Value = $c.Value } }
Write-KrJsonResponse @{ provider = 'azuread'; authenticated = $true; claims = $claims }
}
Start-KrServer -CloseLogsOnExit
3. Endpoints & Tokens
- Authorization:
/authorize→ user login + consent; returnscode - Token:
/token→ exchangecodeforaccess_token(+id_tokenfor OIDC providers,refresh_tokenif enabled) - Provider APIs:
/user,/user/emails(GitHub) for enrichment when permitted by scope
Tokens:
- Access Token: presented to APIs; not identity by itself; may be opaque or JWT
- Refresh Token: long‑lived token to obtain new access tokens (web apps should treat carefully)
3.1 Provider checklist
| Item | Description | Example(s) |
|---|---|---|
| AuthorizationEndpoint | Where the browser is redirected to sign in | https://github.com/login/oauth/authorize |
| TokenEndpoint | Backend exchange of code → tokens | https://github.com/login/oauth/access_token |
| UserInfo/Profile API | Optional enrichment endpoint | https://api.github.com/user |
| CallbackPath | Redirect URI path registered in the app | /signin-oauth |
| Scopes | Permissions requested | GitHub: read:user, user:email; Google: openid email profile |
| Common claims | Useful fields to map | sub, name, email, provider-specific IDs |
4. Scopes & enrichment
- Typical default scopes:
read:user,user:email(for GitHub). Add with-Scope. - Email enrichment: wrapper can fetch primary/verified emails if missing and permitted by scope; disable via
-DisableEmailEnrichment.
5. Protecting routes
- Use
-AuthorizationScheme 'GitHub'(policy scheme) on protected endpoints. - Unauthenticated requests are challenged (302 to GitHub); authenticated requests use the session cookie.
6. Custom providers
- Use Add-KrOAuth2Authentication to configure arbitrary providers.
- Set
AuthorizationEndpoint,TokenEndpoint,ClientId,ClientSecret,CallbackPath, andScope. - Optionally enable
SaveTokens,UsePkce, and addGetClaimsFromUserInfoEndpointif the provider supports a userinfo endpoint.
7. Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| Callback mismatch | App uses different path | Set -CallbackPath to match and update app settings |
| No email claim | Scope not granted / email private | Include user:email scope and ensure account has a primary |
| Infinite redirect loop | Cookie blocked or wrong scheme | Allow cookies; use -AuthorizationScheme 'GitHub' |
| 401 after login | Session lost | Same-site or domain issues; test in a fresh profile |
8. Security best practices
- Use HTTPS always; development can rely on a self-signed cert for local testing.
- Keep client secrets out of source control; prefer environment variables or secret stores.
- Request minimal scopes; avoid over-broad access.
- Validate redirect URIs and add CSRF protections if you add custom state parameters.
8.1 Provider setup checklist
- Register an OAuth app (web application) at the provider
- Add exact redirect URI (default is
/signin-oauthon your host/port) - Create client secret and keep it out of source (env vars)
- Identify endpoints:
AuthorizationEndpoint,TokenEndpoint, and anyUserInformationEndpoint - Enable PKCE and save tokens when appropriate for your app
- Start minimal: request least scopes; add enrichment scopes as needed