Skip to content

Instantly share code, notes, and snippets.

@R4wm
Last active April 17, 2026 16:35
Show Gist options
  • Select an option

  • Save R4wm/0c7cbfcbd658ba1d7d1b1eb8cac2b536 to your computer and use it in GitHub Desktop.

Select an option

Save R4wm/0c7cbfcbd658ba1d7d1b1eb8cac2b536 to your computer and use it in GitHub Desktop.
IDP SSO Login Flow — Play / Parler Social (tokens, payloads, cookies at each step)
digraph IDP_SSO_Flow {
rankdir=TB;
fontname="Helvetica";
node [fontname="Helvetica", fontsize=11, shape=box, style="rounded,filled", fillcolor="#f5f5f5"];
edge [fontname="Helvetica", fontsize=9];
label="IDP SSO Login Flow — Play / Parler Social\n(Tokens, Payloads, Cookies at Each Step)";
labelloc=t;
fontsize=16;
// ─── Actors ───
subgraph cluster_mobile {
label="Mobile App\n(play-phoenix-mobile)";
style="dashed";
color="#4a90d9";
fontcolor="#4a90d9";
mobile_start [label="User taps\n\"Sign in with Parler ID\"", fillcolor="#dbeafe"];
mobile_open_browser [label="WebBrowser.openAuthSessionAsync()\nOpens system browser\n(CCT on Android, SFSafariVC on iOS)", fillcolor="#dbeafe"];
mobile_listen [label="Listening for deep link:\nplaytv://auth/callback\nor parlersocial://auth/callback", fillcolor="#dbeafe"];
mobile_parse [label="Parse callback URL params:\naccess_token, refresh_token,\nauth_type, expires_in", fillcolor="#dbeafe"];
mobile_branch [label="Check auth_type", shape=diamond, fillcolor="#fef3c7"];
mobile_exchange [label="Exchange IDP JWT\nfor Passport tokens", fillcolor="#dbeafe"];
mobile_store [label="Store Passport tokens\nin AsyncStorage\nUser is logged in", fillcolor="#bbf7d0"];
}
subgraph cluster_idp {
label="IDP Service\n(parler-identity-service)";
style="dashed";
color="#e74c3c";
fontcolor="#e74c3c";
idp_session_check [label="GET /api/v1/session/check\n?client_id={id}&platform=mobile", fillcolor="#fde2e2"];
idp_check_cookie [label="Check idp_token cookie\n(HttpOnly, Secure, SameSite=Strict)\nContains: IDP JWT", fillcolor="#fde2e2"];
idp_verify_jwt [label="Verify IDP JWT\n(RSA-256 signature)\nClaims: ulk, identifier,\nsession_id, exp, iat, nbf, jti", fillcolor="#fde2e2"];
idp_branch [label="Session\nvalid?", shape=diamond, fillcolor="#fef3c7"];
idp_no_session [label="No session or expired\nRedirect with:\n#action=unauthorized", fillcolor="#fecaca"];
idp_account_picker [label="Account Picker\n(if multiple linked accounts\nor platform=mobile)\nJSON: linked_accounts[], \ndefault_identifier, ulk", fillcolor="#fde2e2"];
idp_continue [label="POST /api/v1/session/continue\nUser selects account\nBody: { identifier, client_id }", fillcolor="#fde2e2"];
idp_encrypt [label="Encrypt tokens with\nAES-256-GCM\nKey: SHA256(client_secret)\nor raw 32-byte secret\nNonce: 12 random bytes", fillcolor="#fde2e2"];
idp_redirect [label="Redirect to platform redirect_url\nFragment: #action=ok\n&payload={base64(nonce+ciphertext+tag)}", fillcolor="#fde2e2"];
}
subgraph cluster_web_bridge {
label="Web Callback Bridge\n(playtv-vue-ui / social-vue-ui)\nIDPCallback.vue";
style="dashed";
color="#8e44ad";
fontcolor="#8e44ad";
web_receive [label="Receive redirect at\n/idp/callback\nParse URL fragment (#) params\naction, payload", fillcolor="#f3e8ff"];
web_branch [label="action?", shape=diamond, fillcolor="#fef3c7"];
web_unauthorized [label="action=unauthorized\nRedirect to OTP login\n(login form in browser)", fillcolor="#fecaca"];
web_ok [label="action=ok\nExtract encrypted payload", fillcolor="#f3e8ff"];
web_post [label="POST /v3/{app}/authenticate\nBody: { \"payload\": \"<base64>\" }\nVia Laravel proxy or direct", fillcolor="#f3e8ff"];
web_deeplink [label="Build deep link:\n{scheme}://auth/callback\n?access_token={passport_token}\n&refresh_token={passport_refresh}\n&expires_in=3600\n&auth_type=idp_session\nwindow.location.href = deepLink", fillcolor="#f3e8ff"];
}
subgraph cluster_laravel {
label="Laravel Proxy\n(social-api)";
style="dashed";
color="#95a5a6";
fontcolor="#95a5a6";
laravel_proxy [label="GoApiProxyController\nChecks GO_API_ACTIVE=true\nForwards to GO_API_BASE_URL\n/v3/{app}/authenticate", fillcolor="#ecf0f1"];
}
subgraph cluster_go {
label="Go Backend\n(go-social-api)";
style="dashed";
color="#27ae60";
fontcolor="#27ae60";
go_authenticate [label="POST /v3/{app}/authenticate\nIDPAuthenticateHandler\nReceives: { payload }", fillcolor="#d5f5e3"];
go_decrypt [label="Decrypt AES-256-GCM\n1. Base64 decode payload\n2. Extract nonce (first 12 bytes)\n3. Extract ciphertext (rest)\n4. Key = SHA256(IDP_CLIENT_SECRET_{APP})\n5. gcm.Open() -> plaintext JSON", fillcolor="#d5f5e3"];
go_extract [label="Extract from decrypted JSON:\n{\n access_token: \"IDP JWT\",\n refresh_token: \"...\",\n expires_in: 900,\n refresh_expires_in: 604800\n}", fillcolor="#d5f5e3"];
go_verify_jwt [label="Verify IDP JWT\n1. Decode header -> kid\n2. Fetch JWKS from IDP_JWKS_URL\n (cached 1hr per app)\n3. RSA-256 signature verify\n4. Validate: iss, exp, nbf\n5. aud check SKIPPED\n (IDP has no aud claim)", fillcolor="#d5f5e3"];
go_lookup [label="User lookup by IDP ULK:\n1. FindByExternalIdentity(\"idp\", ulk)\n2. If not found: bootstrap by\n email or phone from JWT claims\n3. CreateExternalIdentityLink()\n if bootstrapping", fillcolor="#d5f5e3"];
go_mint [label="Mint Passport tokens\nissueTokenForUser(user, \"idp\")\nPassport JWT claims:\nsub, ulk, email, iat, exp, jti\nAlgo: HS256", fillcolor="#d5f5e3"];
go_respond [label="Response 200:\n{\n access_token: \"Passport JWT\",\n refresh_token: \"Passport refresh\",\n expires_in: 3600,\n refresh_expires_in: 43200\n}", fillcolor="#d5f5e3"];
go_idp_login [label="POST /v3/{app}/login/idp\nIDPVerifyHandler\nBody: { token: \"IDP JWT\" }\n(OTP/direct exchange path)", fillcolor="#abebc6"];
}
subgraph cluster_otp {
label="OTP Login Path\n(no existing IDP session)";
style="dashed";
color="#f39c12";
fontcolor="#f39c12";
otp_login [label="User enters email/phone\nin IDP login form\nReceives OTP code", fillcolor="#fef9e7"];
otp_verify [label="OTP verified by IDP\nIDP issues JWT\nSets idp_token cookie\nRedirects with tokens", fillcolor="#fef9e7"];
otp_deeplink [label="Deep link back to app:\n{scheme}://auth/callback\n?access_token={IDP_JWT}\n&auth_type=idp_login", fillcolor="#fef9e7"];
}
// ─── Edges: Session Check Path (happy path) ───
mobile_start -> mobile_open_browser [label="tap"];
mobile_open_browser -> idp_session_check [label="Opens URL:\n{IDP_BASE_URL}/session/check\n?client_id={id}&platform=mobile", color="#4a90d9"];
idp_session_check -> idp_check_cookie [label="Read cookies"];
idp_check_cookie -> idp_branch;
idp_branch -> idp_no_session [label="No cookie\nor expired", color="#e74c3c"];
idp_branch -> idp_verify_jwt [label="Cookie\npresent", color="#27ae60"];
idp_verify_jwt -> idp_account_picker [label="Valid session\nmultiple accounts\nor platform=mobile"];
idp_account_picker -> idp_continue [label="User taps\nan account"];
idp_continue -> idp_encrypt [label="Account selected\nGenerate verify token (JWT)\nwith sub=selected_ulk"];
idp_encrypt -> idp_redirect [label="Encrypted payload ready"];
idp_redirect -> web_receive [label="HTTP redirect\nto platform redirect_url\n(e.g. play.parler.com/idp/callback\nor app.forte.ws/idp/callback)", color="#8e44ad"];
// ─── Edges: Web Bridge ───
web_receive -> web_branch;
web_branch -> web_unauthorized [label="unauthorized"];
web_branch -> web_ok [label="ok + payload"];
web_ok -> web_post;
web_post -> laravel_proxy [label="POST /v3/{app}/authenticate\n{ payload: \"<encrypted>\" }", color="#95a5a6"];
laravel_proxy -> go_authenticate [label="Forward request\nto GO_API_BASE_URL", color="#95a5a6"];
go_authenticate -> go_decrypt;
go_decrypt -> go_extract;
go_extract -> go_verify_jwt;
go_verify_jwt -> go_lookup;
go_lookup -> go_mint;
go_mint -> go_respond;
go_respond -> web_deeplink [label="200 OK\n{ access_token, refresh_token }", color="#8e44ad"];
web_deeplink -> mobile_listen [label="Deep link redirect:\n{scheme}://auth/callback\n?access_token={PASSPORT}\n&refresh_token={PASSPORT_REFRESH}\n&auth_type=idp_session", color="#4a90d9"];
mobile_listen -> mobile_parse;
mobile_parse -> mobile_branch;
mobile_branch -> mobile_store [label="auth_type=idp_session\nTokens are already\nPassport tokens\nStore directly"];
// ─── Edges: OTP Path ───
idp_no_session -> web_receive [label="Redirect with\n#action=unauthorized", style=dashed, color="#f39c12"];
web_unauthorized -> otp_login [label="Show login form\nin browser", style=dashed, color="#f39c12"];
otp_login -> otp_verify [label="OTP code entered", style=dashed, color="#f39c12"];
otp_verify -> otp_deeplink [label="Sets idp_token cookie\nRedirect with IDP JWT", style=dashed, color="#f39c12"];
otp_deeplink -> mobile_listen [label="Deep link:\n{scheme}://auth/callback\n?access_token={IDP_JWT}\n&auth_type=idp_login", style=dashed, color="#f39c12"];
mobile_branch -> mobile_exchange [label="auth_type=idp_login\nToken is IDP JWT\nNeeds exchange", style=dashed, color="#f39c12"];
mobile_exchange -> go_idp_login [label="POST /v3/{app}/login/idp\n{ token: \"<IDP JWT>\" }", style=dashed, color="#f39c12"];
go_idp_login -> go_verify_jwt [label="Same JWT verify\n+ user lookup +\nPassport mint", style=dashed, color="#f39c12"];
go_respond -> mobile_exchange [label="200 OK\n{ access_token, refresh_token }\n(Passport tokens)", style=dashed, color="#f39c12"];
mobile_exchange -> mobile_store [label="Store Passport tokens", style=dashed, color="#f39c12"];
// ─── Legend ───
subgraph cluster_legend {
label="Legend";
style="filled";
fillcolor="#fafafa";
color="#cccccc";
fontsize=12;
legend_solid [label="Session Check Path\n(existing IDP session)", shape=plaintext, fillcolor="#fafafa"];
legend_dashed [label="OTP Login Path\n(no session, first-time)", shape=plaintext, fillcolor="#fafafa"];
}
subgraph cluster_tokens {
label="Token Types";
style="filled";
fillcolor="#fafafa";
color="#cccccc";
fontsize=12;
tok1 [label="idp_token cookie\nIDP JWT (RSA-256)\nClaims: ulk, identifier,\nsession_id, exp", shape=note, fillcolor="#fde2e2"];
tok2 [label="Encrypted Payload\nAES-256-GCM\nBase64(nonce+ciphertext+tag)\nContains IDP JWT + refresh", shape=note, fillcolor="#fff3cd"];
tok3 [label="Passport JWT (HS256)\nFinal app token\nClaims: sub, ulk, email,\niat, exp, jti", shape=note, fillcolor="#d5f5e3"];
}
subgraph cluster_env {
label="Critical Env Vars";
style="filled";
fillcolor="#fafafa";
color="#cccccc";
fontsize=12;
env1 [label="IDP Service:\nplatforms.client_secret\nplatforms.client_id\nplatforms.redirect_url", shape=note, fillcolor="#ecf0f1"];
env2 [label="Go Backend:\nIDP_CLIENT_SECRET_{APP}\nIDP_JWKS_URL\nIDP_ISSUER\nIDP_LOGIN_ENABLED_{APP}\n(NO IDP_AUDIENCE!)", shape=note, fillcolor="#ecf0f1"];
env3 [label="Laravel Proxy:\nGO_API_ACTIVE=true\nGO_API_BASE_URL=<go backend>", shape=note, fillcolor="#ecf0f1"];
env4 [label="Web Bridge (Vite):\nVITE_API_HOST\nVITE_IDP_BASE_URL\nVITE_IDP_CLIENT_ID\n(baked at BUILD TIME!)", shape=note, fillcolor="#ecf0f1"];
}
}
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment