Skip to content

feat: expose OIDC end-session endpoint - #1102

Draft
norrs wants to merge 15 commits into
tinyauthapp:mainfrom
norrs:feat/oidc-end-session
Draft

norrs wants to merge 15 commits into
tinyauthapp:mainfrom
norrs:feat/oidc-end-session

Conversation

@norrs

@norrs norrs commented Aug 30, 2026

Copy link
Copy Markdown

Stack

This PR depends on #1094 and must merge after it. Because the branches are on a fork, GitHub requires this PR to target main; its Files view will include the parent until #1094 merges. Review the endpoint-only change here:

norrs/tinyauth@feat/sso-logout...feat/oidc-end-session

Summary

  • advertise end_session_endpoint in OIDC discovery
  • validate downstream ID token hints and registered post-logout redirect URIs
  • reuse the upstream provider logout cascade for OIDC clients
  • protect provider callbacks with short-lived, one-time tickets
  • cover the endpoint, validation, redirects, discovery metadata, and logout cascade

Verification

  • make test
  • make vet
  • go test -count=1 ./internal/controller ./internal/service

norrs and others added 15 commits August 25, 2026 00:33
OAuth-backed sessions currently only log out of Tinyauth. When the
upstream provider keeps its SSO session, the next protected application
access can immediately create a new Tinyauth session, so logout does not
behave like an end-to-end sign-out for OIDC providers that support
RP-initiated logout.

Add an optional OAuth provider logoutUrl for the OpenID Provider
end_session_endpoint and keep the provider id_token server-side on the
Tinyauth session. The logout handler now deletes the local session,
builds the OP logout request with client_id, id_token_hint,
post_logout_redirect_uri, and state, and returns that redirect to the
frontend. The callback endpoint validates and restores the requested
application return URL after the OP hop.

Persist oauth_id_token for SQLite, Postgres, memory, SQLC generated
repositories, and store wrapper models so refreshed sessions retain the
token. Add migrations for both database drivers. Update the logout page
and quick actions menu to follow backend-provided redirect URLs while
keeping Tinyauth redirect_uri separate from OIDC
post_logout_redirect_uri.

Cover the new behavior with controller tests for safe logout redirects,
logout URL construction, and use of the server-side id_token. Enable TLS
on the dev whoami route so the local Traefik setup exercises the
secure-cookie and OIDC logout flow.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Refs: tinyauthapp#1094 (comment)

Spec: OpenID Connect RP-Initiated Logout 1.0 end_session_endpoint MUST use https.

Co-Authored-By: OpenAI Codex <codex@openai.com>
Refs: tinyauthapp#1094 (comment)

Co-Authored-By: OpenAI Codex <codex@openai.com>
Refs: tinyauthapp#1094 (comment)

Co-Authored-By: OpenAI Codex <codex@openai.com>
Refs: tinyauthapp#1094 (comment)

Refs: tinyauthapp#1094 (comment)

Co-Authored-By: OpenAI Codex <codex@openai.com>
Refs: tinyauthapp#1094 (comment)

Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Advertise RP-initiated logout in discovery, validate downstream ID token hints and registered post-logout redirects, and reuse the upstream logout cascade with one-time callback tickets.

Co-Authored-By: OpenAI Codex <codex@openai.com>
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@steveiliop56 steveiliop56 added this to the v5.3.0 milestone Sep 13, 2026
@steveiliop56

Copy link
Copy Markdown
Member

Note to myself

Will need to review the https://openid.net/specs/openid-connect-rpinitiated-1_0.html document before reviewing this.

@bsaurusrex

Copy link
Copy Markdown

Note

AI-generated review hint. This comment was written by an AI assistant (Claude, model Opus 5.5) and posted by me, per AI_POLICY.md. It's offered only as a head start for the spec review mentioned above, not as a request for changes. I reviewed the endpoint-only diff (feat/sso-logout...feat/oidc-end-session, d6732ee) against OpenID Connect RP-Initiated Logout 1.0.

Looks conforming

  • end_session_endpoint is advertised in discovery (§2.1).
  • The endpoint accepts both GET and POST (§2: the OP MUST support both).
  • id_token_hint is checked for signature and iss (§2: MUST validate that the OP issued it), and exp is deliberately not checked (§2: SHOULD accept expired hints).
  • client_id must match the hint's aud when both are sent (§2: MUST).
  • post_logout_redirect_uri must exactly match a registered URI, and is never used otherwise (§3: MUST NOT redirect on a mismatch).
  • state is passed back to the RP (§3).
  • Without a valid hint, the user is asked to confirm instead of being logged out silently (§2, Security Considerations: unauthenticated logout is a DoS vector).
  • A hint whose sub doesn't match the current session also falls back to confirmation.

Worth considering

  1. The POST variant may not actually end the session. The session cookie is SameSite=Lax, so a cross-site form POST from the RP arrives without it. sessionID is then empty, Logout logs "treating as successful logout" and redirects to the RP, but the Tinyauth session stays alive. The user context is also nil in that case, so the sub mismatch check is skipped too. GET (a top-level navigation) is fine. One option is to answer a POST with a 303 redirect to the GET form with the same params, so the follow-up navigation carries the cookie. That's worth verifying in a browser.
  2. Rebase onto main. The branch now conflicts in oidc_controller.go, and refactor: use provider id in oidc sub #1183 changed CreateSub (provider ID in the sub, plus LegacySubEnabled). The hint-vs-context comparison calls service.CreateSub, so it picks up the new format automatically. Any test fixtures that hard-code the old username:clientId sub will need updating.
  3. Hints for expired RP sessions. ValidateEndSessionRequest requires a live oidc_sessions row for the hint's sub. Once that row is swept, a correctly signed hint is treated as invalid: the user gets the confirmation page and the RP redirect is dropped. §2 says the OP SHOULD accept hints when the RP "has a current session or had a recent session". That's allowed (it's a SHOULD), but stricter than the spec suggests.
  4. client_id without a hint. §3 allows the OP to validate post_logout_redirect_uri using client_id alone. Right now that case goes to /logout, which drops post_logout_redirect_uri and state, so the user ends up on Tinyauth instead of back at the RP. This is optional, and the current behaviour is conforming.
  5. Error responses are JSON. An invalid request returns a JSON 400 to what is a browser navigation. /authorize sends errors to the frontend error page instead, which may be the nicer pattern here. This is UX only, not a spec issue.
  6. Tokens stay valid (no action needed). End-session deletes the browser session only. The RP's access and refresh tokens stay valid until they expire. RP-Initiated Logout doesn't require revocation (that's Back-Channel Logout / RFC 7009 territory). Mentioning it so it's a conscious decision.

ui_locales isn't bound and logout_hint is accepted but unused. Both are OPTIONAL, so that's fine.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants