feat(auth): add SSO token exchange endpoint - #1134
Merged
qwqcode merged 3 commits intoJun 14, 2026
Merged
Conversation
Adds POST /api/v2/sso/exchange — accepts an access token from an
external OIDC IdP (verified by calling the IdP's /userinfo) and issues
an Artalk session JWT for the user identified by the email claim.
Use case: a surrounding application that already runs OIDC (e.g. an
Auth0 SPA) can drive Artalk sign-in without showing Artalk's own popup
login UI. The widget reads the resulting JWT from
localStorage["ArtalkUser"].token and treats the user as fully
authenticated for is_login/is_admin checks.
Implementation:
- One new handler at server/handler/auth_sso_exchange.go (~110 lines)
- Reuses dao.FindCreateUser + common.LoginGetUserToken; same JWT
format as /user/access_token, so no client-side changes needed
- Validation by calling {issuer}/userinfo with Authorization: Bearer
— OIDC-standard, no JWKS dep added
- Gated by auth.enabled && auth.sso.enabled (404 otherwise)
- Returns the same ResponseUserLogin shape as /auth/email/login
Config additions (conf/artalk.example.yml):
auth:
sso:
enabled: false
issuer: "" # e.g. "tenant.auth0.com"
Route registered alongside the existing social-login routes in
server/server.go.
Verified by go build ./... (clean).
Required so koanf actually maps ATK_AUTH_SSO_ENABLED/ISSUER env vars to the new auth.sso config block introduced in feat: add SSO token exchange endpoint. Without this regen the env vars are silently ignored and the endpoint stays in its disabled state.
qwqcode
force-pushed
the
upstream-pr/sso-token-exchange
branch
from
June 14, 2026 10:17
f2599a4 to
e3b4757
Compare
Member
|
I've added some additional documentation, thanks for the PR! ❤️ |
qwqcode
approved these changes
Jun 14, 2026
thun888
pushed a commit
to thun888/Artalk
that referenced
this pull request
Jul 28, 2026
* feat(auth): add SSO token exchange endpoint
Adds POST /api/v2/sso/exchange — accepts an access token from an
external OIDC IdP (verified by calling the IdP's /userinfo) and issues
an Artalk session JWT for the user identified by the email claim.
Use case: a surrounding application that already runs OIDC (e.g. an
Auth0 SPA) can drive Artalk sign-in without showing Artalk's own popup
login UI. The widget reads the resulting JWT from
localStorage["ArtalkUser"].token and treats the user as fully
authenticated for is_login/is_admin checks.
Implementation:
- One new handler at server/handler/auth_sso_exchange.go (~110 lines)
- Reuses dao.FindCreateUser + common.LoginGetUserToken; same JWT
format as /user/access_token, so no client-side changes needed
- Validation by calling {issuer}/userinfo with Authorization: Bearer
— OIDC-standard, no JWKS dep added
- Gated by auth.enabled && auth.sso.enabled (404 otherwise)
- Returns the same ResponseUserLogin shape as /auth/email/login
Config additions (conf/artalk.example.yml):
auth:
sso:
enabled: false
issuer: "" # e.g. "tenant.auth0.com"
Route registered alongside the existing social-login routes in
server/server.go.
Verified by go build ./... (clean).
* chore(config): regenerate env-path cache with auth.sso entries
Required so koanf actually maps ATK_AUTH_SSO_ENABLED/ISSUER env vars
to the new auth.sso config block introduced in feat: add SSO token
exchange endpoint. Without this regen the env vars are silently
ignored and the endpoint stays in its disabled state.
* docs: add sso token exchange section for auth docs
---------
Co-authored-by: qwqcode <qwqcode@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1133.
Adds
POST /api/v2/sso/exchange— accepts an external OIDC IdP accesstoken (validated by calling the IdP's
/userinfo) and returns thesame
ResponseUserLoginshape/api/v2/user/access_tokenand thesocial-login callback already return. The frontend can write the
returned token into
localStorage["ArtalkUser"].tokenand the widgettreats the user as fully logged-in (no popup, no admin password
prompt for admin users), exactly the same way
loginByTokendoesafter the popup callback today.
See #1133 for the use case discussion. tl;dr: lets a surrounding app
that already has an Auth0/Keycloak/Okta session embed Artalk without
showing Artalk's own login UI.
What's in the diff
server/handler/auth_sso_exchange.go{issuer}/userinfo, extractsemail,FindCreateUser,LoginGetUserToken.internal/config/config.goAuthSSOConf{ Enabled, Issuer }struct, attached toAuthConfasSSO.internal/config/cache.gogo run internal/config/meta/gen/main.go -locale en -format go -o internal/config/cache.goto pick up the new env-path mappings (ATK_AUTH_SSO_ENABLED,ATK_AUTH_SSO_ISSUER).conf/artalk.example.ymlauth.sso:block documenting the opt-in.server/server.goh.AuthSSOExchange(app, api)next toh.AuthSocialLogin.API surface
Same response shape as
/api/v2/auth/email/loginso the frontend'sexisting
loginByApiReshelper consumes it without change.Backward compatibility
Gated off by default —
auth.sso.enabledisfalsein the exampleconfig, and the handler returns 404 unless both
auth.enabledandauth.sso.enabledare true. Nothing else in the codebase reads thenew fields. Existing deploys are unaffected.
Why no JWKS dependency
OIDC's
/userinfois the spec-defined way to validate an accesstoken at a relying party — the IdP signs the response server-side
and bounces non-200 on invalid/revoked tokens. Adding a JWKS
verifier (e.g.
MicahParks/keyfunc) would let us avoid theround-trip but pulls in a dep and a key-rotation cache I didn't
want to maintain in the same PR. Happy to follow up with JWKS as
a perf optimisation if you'd take it.
Tested against
Auth0 tenant
sparklesparkle.auth0.com, running this fork inproduction on Railway since 2026-05-19. The endpoint is also live
right now at https://artalk-production-4ade.up.railway.app/api/v2/sso/exchange
— a quick
curl -X POST -d '{"token":"x"}'returns{"msg":"SSO token rejected by issuer"}as expected (Auth0 401 → 401to the caller). Real tokens from the Auth0 SPA SDK round-trip
correctly and return Artalk JWTs that the widget accepts.
Out of scope (could be follow-ups)
audclaim check (currently relies entirely on/userinforejection)picture, etc.)Happy to iterate on naming, location, or scope.