Use case
A surrounding application that already authenticates users with an
OIDC IdP (Auth0, Keycloak, Okta, etc.) wants to embed Artalk and have
the same sign-in cover both โ i.e., users who are already logged in
to the parent app post comments without a second click, without seeing
Artalk's own login UI, and without the comment widget showing a
"Login with X" button.
Concrete example: a static documentation site running an Auth0 SPA
session in the same browser tab. The Auth0 SPA SDK can hand the parent
app an access token. The parent app would like to swap that for an
Artalk session JWT and pre-populate localStorage["ArtalkUser"].token
before Artalk.init() runs.
What's there today
/api/v2/auth/{provider} opens the OIDC provider in a popup, returns
via internal/auth/callback.go::ResponseCallbackPage which calls
window.opener.postMessage({ type: 'ATK_AUTH_CALLBACK', payload: <jwt> }).
The widget's plugin-auth (ui/plugin-auth/lib/oauth-login.ts) listens
for that message and loginByToken(ctx, token)s the user in.
That popup flow is the only way to land a token in
localStorage["ArtalkUser"] short of typing a password via
/api/v2/user/access_token. The flow itself is sound โ but in
SSO-with-parent-app contexts the popup is jarring (the user is
already authed; opening a popup just to round-trip through a
silent-auth Auth0 redirect feels like a hiccup) and a few real
browsers block the popup without an explicit user gesture each time
(notably Safari's anti-tracking heuristics on third-party origins).
Proposed
Add POST /api/v2/sso/exchange (or similar name โ bike-shed welcome)
that:
- accepts
{ token: <opaque-or-JWT-access-token> },
- validates by calling
{issuer}/userinfo with Authorization: Bearer <token> (OIDC standard; the IdP signs the response, so no JWKS dep on Artalk's side),
- extracts the
email claim,
FindCreateUsers for that name+email,
- mints an Artalk JWT via
common.LoginGetUserToken exactly the same
way /api/v2/user/access_token and the social-login callback do,
- returns
ResponseUserLogin โ same shape as the existing login
endpoints, so the frontend already knows how to consume it.
Config (defaults to off, opt-in only):
auth:
enabled: true
sso:
enabled: false
issuer: "" # e.g. "tenant.auth0.com" or "https://tenant.auth0.com"
Gated by auth.enabled && auth.sso.enabled; 404s on misconfiguration
so it's invisible unless deliberately turned on.
Why this is small
It's purely additive โ one new handler file, four config struct lines,
one line in server.go, one block in conf/artalk.example.yml, one
regen of internal/config/cache.go. No new Go dependencies (uses
the existing net/http to call /userinfo rather than adding a JWKS
library). Backward compatible because the endpoint is gated off by
default.
I've implemented it on a fork
Branch: https://github.com/sparkling/Artalk/tree/upstream-pr/sso-token-exchange
Happy to open a PR against this issue if you'd take the contribution.
The fork's been running in production against an Auth0 tenant since
2026-05-19 and the static-site Comments widget posts cleanly with
zero extra UI โ sign-in via the parent app's Auth0 session is the
only auth surface the user sees.
Alternatives considered
- Frontend popup, but silent: the Auth0 silent-auth flow handles
this when the parent already has a session, but the popup itself
still flickers and Safari blocks it. Doesn't solve the embed-UX
problem.
- A sidecar service that mints JWTs: works, but pushes
app_key outside Artalk (a copy in a second service), which
violates "secret stays where it's used." Putting the endpoint in
Artalk keeps the key in one place.
- Patching Artalk's frontend to load with a pre-supplied token:
the widget already does that (reads
localStorage["ArtalkUser"].token) โ the bottleneck is just
generating the token. Hence the endpoint.
Thoughts on the shape / naming / whether you'd take a PR?
Use case
A surrounding application that already authenticates users with an
OIDC IdP (Auth0, Keycloak, Okta, etc.) wants to embed Artalk and have
the same sign-in cover both โ i.e., users who are already logged in
to the parent app post comments without a second click, without seeing
Artalk's own login UI, and without the comment widget showing a
"Login with X" button.
Concrete example: a static documentation site running an Auth0 SPA
session in the same browser tab. The Auth0 SPA SDK can hand the parent
app an access token. The parent app would like to swap that for an
Artalk session JWT and pre-populate
localStorage["ArtalkUser"].tokenbefore
Artalk.init()runs.What's there today
/api/v2/auth/{provider}opens the OIDC provider in a popup, returnsvia
internal/auth/callback.go::ResponseCallbackPagewhich callswindow.opener.postMessage({ type: 'ATK_AUTH_CALLBACK', payload: <jwt> }).The widget's plugin-auth (
ui/plugin-auth/lib/oauth-login.ts) listensfor that message and
loginByToken(ctx, token)s the user in.That popup flow is the only way to land a token in
localStorage["ArtalkUser"]short of typing a password via/api/v2/user/access_token. The flow itself is sound โ but inSSO-with-parent-app contexts the popup is jarring (the user is
already authed; opening a popup just to round-trip through a
silent-auth Auth0 redirect feels like a hiccup) and a few real
browsers block the popup without an explicit user gesture each time
(notably Safari's anti-tracking heuristics on third-party origins).
Proposed
Add
POST /api/v2/sso/exchange(or similar name โ bike-shed welcome)that:
{ token: <opaque-or-JWT-access-token> },{issuer}/userinfowithAuthorization: Bearer <token>(OIDC standard; the IdP signs the response, so no JWKS dep on Artalk's side),emailclaim,FindCreateUsers for that name+email,common.LoginGetUserTokenexactly the sameway
/api/v2/user/access_tokenand the social-login callback do,ResponseUserLoginโ same shape as the existing loginendpoints, so the frontend already knows how to consume it.
Config (defaults to off, opt-in only):
Gated by
auth.enabled && auth.sso.enabled; 404s on misconfigurationso it's invisible unless deliberately turned on.
Why this is small
It's purely additive โ one new handler file, four config struct lines,
one line in
server.go, one block inconf/artalk.example.yml, oneregen of
internal/config/cache.go. No new Go dependencies (usesthe existing
net/httpto call/userinforather than adding a JWKSlibrary). Backward compatible because the endpoint is gated off by
default.
I've implemented it on a fork
Branch: https://github.com/sparkling/Artalk/tree/upstream-pr/sso-token-exchange
Happy to open a PR against this issue if you'd take the contribution.
The fork's been running in production against an Auth0 tenant since
2026-05-19 and the static-site Comments widget posts cleanly with
zero extra UI โ sign-in via the parent app's Auth0 session is the
only auth surface the user sees.
Alternatives considered
this when the parent already has a session, but the popup itself
still flickers and Safari blocks it. Doesn't solve the embed-UX
problem.
app_keyoutside Artalk (a copy in a second service), whichviolates "secret stays where it's used." Putting the endpoint in
Artalk keeps the key in one place.
the widget already does that (reads
localStorage["ArtalkUser"].token) โ the bottleneck is justgenerating the token. Hence the endpoint.
Thoughts on the shape / naming / whether you'd take a PR?