Skip to main content

Direct OAuth authorization

A policy can send users directly to an OAuth/OIDC provider and manage the application's local session. This mode requires no portal, local database or application JWT signing configuration. It is available in caddy-security v1.3.0 / go-authcrunch v1.3.8.

Use a portal when you need local accounts, portal transforms, profile management, local MFA or downstream OIDC. Direct authorization retains provider claims and checks its ACL on every request; it does not run those portal features.

Register and configure​

Create a confidential provider application supporting authorization code, client_secret_post, and S256 PKCE. For this example register exactly:

https://app.example.com/_authcrunch/oauth2/apppolicy/authorization-code-callback

Set OIDC_ISSUER and OIDC_DISCOVERY_URL to the issuer and its discovery URL; these expand at adaptation time. Set OIDC_CLIENT_ID and OIDC_CLIENT_SECRET to the provider credentials; their runtime placeholders stay in the example. Configure an ID-token group/role app-members for intended users and provide email for the generic driver's default check. See the generic provider guide for claim-source requirements.

assets/conf/oauth/direct/Caddyfile
# Direct OIDC login without a portal: caddy-security v1.3.0.
{
security {
oauth identity provider upstream {
driver generic
realm upstream
client_id {env.OIDC_CLIENT_ID}
client_secret {env.OIDC_CLIENT_SECRET}
base_auth_url {$OIDC_ISSUER}
metadata_url {$OIDC_DISCOVERY_URL}
scopes openid email profile groups
}

authorization policy apppolicy {
use oauth identity provider upstream
oauth public origin https://app.example.com
oauth base path /_authcrunch/oauth2/apppolicy
oauth session lifetime 900
oauth maximum sessions 10000
oauth maximum pending logins 1024
allow roles app-members
}
}
}

app.example.com {
route {
authorize with apppolicy
reverse_proxy 127.0.0.1:8080
}
}

The whole site routes through one policy, including its callback and logout namespace. If you protect only selected app paths, explicitly route the OAuth namespace to the same policy too. Do not strip or rewrite its base path.

Decide who may enter​

The example requires app-members, supplied by the verified provider identity. Test a member and nonmember. Direct policies add authp/user to authenticated users; allowing that baseline alone admits every account the provider accepts. It is not a restricted application membership rule.

Provider group configuration, app assignment, and the AuthCrunch ACL are separate checks. Direct policies cannot use a portal transform to add a missing claim. Generic OIDC does not forward arbitrary path-ACL claims; use policy method/path rules for resource restrictions instead.

Request and session behavior​

An anonymous GET or HEAD starts provider login with a 302. A successful callback returns 303 to the saved local URI. Unauthenticated requests with other methods receive 401; request bodies are not replayed after login. A denied identity or resource receives 403. Malformed callbacks return 400, unsupported methods 405, provider initiation failures 502, and capacity exhaustion 503.

The policy creates host-only, root-path, Secure, HttpOnly, SameSite=Lax session and login cookies. The browser receives an opaque local session credential, not an upstream token. Sessions are bound to the policy and HTTPS origin. An upstream account change does not instantly revoke a completed local session; choose its lifetime accordingly.

Setting inside the policyDefault
oauth base path/_authcrunch/oauth2/POLICY_NAME
oauth session lifetime900 seconds, maximum 86400
oauth maximum sessions10000
oauth maximum pending logins1024, including in-flight exchanges
oauth session cookie nameAUTHZ_POLICY_NAME_SESSION
oauth login cookie nameAUTHZ_POLICY_NAME_LOGIN

oauth public origin pins the external HTTPS origin. Behind TLS termination, preserve the expected Host through the trusted proxy. Without an explicit origin, the incoming connection must use TLS and the site must restrict valid hosts.

JWT keys, token-source lists, portal cookie settings and authentication proxies cannot be combined with direct OAuth in the same policy. Normal claim injection, token stripping, ACLs and bypass rules remain separate policy options. The reserved OAuth namespace is processed before bypass rules.

Sign out and restart​

POST to /_authcrunch/oauth2/apppolicy/logout with the exact same-origin Origin header. In a browser:

await fetch('/_authcrunch/oauth2/apppolicy/logout', {
method: 'POST',
credentials: 'same-origin',
});

A successful response is 204. It revokes the local session and pending login, then deletes both policy cookies. It does not end the provider's SSO session. There is no sliding renewal or refresh token in this mode; expiry starts a new provider login, which may reuse upstream SSO.

Without persistent state, restart loses sessions and pending logins. Persistence retains completed sessions and logout, but unfinished exchanges still restart. Keep browser callbacks and relying requests on the same process; there is no distributed session store.

Verify the deployment​

Check the exact callback URL, member access, nonmember denial, POST without a session, expiry, local logout, and callback replay rejection. Confirm that the OAuth namespace reaches the policy rather than your upstream. A parser check does not establish live provider registration, consent or group configuration.