Skip to content

Instantly share code, notes, and snippets.

@jalehman
Created January 30, 2026 20:59
Show Gist options
  • Select an option

  • Save jalehman/f290044b1012bc75f1d0092e91a9bf56 to your computer and use it in GitHub Desktop.

Select an option

Save jalehman/f290044b1012bc75f1d0092e91a9bf56 to your computer and use it in GitHub Desktop.
LSE Identity Architecture - Option A (Separate Audience)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LSE Identity Architecture — Option A (Separate Audience)</title>
<style>
:root {
--bg: #0f172a;
--surface: #1e293b;
--surface2: #334155;
--border: #475569;
--text: #e2e8f0;
--text-dim: #94a3b8;
--accent: #38bdf8;
--accent2: #818cf8;
--accent3: #34d399;
--accent4: #fb923c;
--accent5: #f472b6;
--danger: #f87171;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
padding: 24px;
line-height: 1.6;
}
h1 { font-size: 1.6em; margin-bottom: 4px; color: var(--accent); }
h2 { font-size: 1.2em; margin: 32px 0 16px; color: var(--accent2); border-bottom: 1px solid var(--surface2); padding-bottom: 8px; }
h3 { font-size: 1em; margin: 16px 0 8px; color: var(--accent3); }
.subtitle { color: var(--text-dim); font-size: 0.9em; margin-bottom: 24px; }
.diagram {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
margin: 16px 0;
overflow-x: auto;
}
.flow-container {
display: flex;
flex-direction: column;
gap: 24px;
min-width: 700px;
}
.row {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: nowrap;
}
.box {
border: 2px solid var(--border);
border-radius: 10px;
padding: 12px 16px;
text-align: center;
min-width: 140px;
position: relative;
}
.box .label { font-weight: 600; font-size: 0.9em; }
.box .detail { font-size: 0.75em; color: var(--text-dim); margin-top: 4px; }
.box.auth0 { border-color: var(--accent4); background: rgba(251,146,60,0.08); }
.box.identity { border-color: var(--accent); background: rgba(56,189,248,0.08); }
.box.app { border-color: var(--accent2); background: rgba(129,140,248,0.08); }
.box.db { border-color: var(--accent3); background: rgba(52,211,153,0.08); }
.box.m2m { border-color: var(--accent5); background: rgba(244,114,182,0.08); }
.arrow {
color: var(--text-dim);
font-size: 0.8em;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
min-width: 80px;
}
.arrow .arrow-line { font-size: 1.2em; letter-spacing: -2px; }
.arrow .arrow-label { font-size: 0.7em; text-align: center; max-width: 100px; }
.v-arrow {
display: flex;
flex-direction: column;
align-items: center;
color: var(--text-dim);
font-size: 0.75em;
padding: 4px 0;
}
.v-arrow .v-line { font-size: 1.2em; }
.section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin: 12px 0;
}
.config-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
}
@media (max-width: 600px) {
.config-grid { grid-template-columns: 1fr; }
}
.config-item {
background: var(--surface2);
border-radius: 8px;
padding: 12px;
}
.config-item .key { color: var(--accent); font-weight: 600; font-size: 0.85em; }
.config-item .val { color: var(--text); font-size: 0.85em; margin-top: 4px; }
.config-item .note { color: var(--text-dim); font-size: 0.75em; margin-top: 4px; }
.step-list {
list-style: none;
counter-reset: steps;
}
.step-list li {
counter-increment: steps;
padding: 12px 0 12px 48px;
position: relative;
border-bottom: 1px solid var(--surface2);
}
.step-list li:last-child { border-bottom: none; }
.step-list li::before {
content: counter(steps);
position: absolute;
left: 0;
top: 12px;
width: 32px;
height: 32px;
background: var(--surface2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.85em;
color: var(--accent);
}
.step-list li strong { color: var(--accent3); }
code {
background: var(--surface2);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.85em;
color: var(--accent);
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7em;
font-weight: 600;
text-transform: uppercase;
}
.badge.new { background: rgba(52,211,153,0.2); color: var(--accent3); }
.badge.exists { background: rgba(148,163,184,0.2); color: var(--text-dim); }
.badge.m2m-badge { background: rgba(244,114,182,0.2); color: var(--accent5); }
.token-table {
width: 100%;
border-collapse: collapse;
margin-top: 12px;
font-size: 0.85em;
}
.token-table th {
text-align: left;
padding: 8px 12px;
background: var(--surface2);
color: var(--accent);
font-weight: 600;
}
.token-table td {
padding: 8px 12px;
border-bottom: 1px solid var(--surface2);
}
.token-table tr:last-child td { border-bottom: none; }
</style>
</head>
<body>
<h1>🏗️ LSE Identity Architecture</h1>
<p class="subtitle">Option A — Separate Auth0 API audience for identity-api</p>
<!-- SYSTEM DIAGRAM -->
<h2>System Overview</h2>
<div class="diagram">
<div class="flow-container">
<!-- Top row: LSE Apps -->
<div class="row">
<div class="box app">
<div class="label">🎳 Bowler Connect</div>
<div class="detail">connect.luckystrike.cloud</div>
<div class="detail"><span class="badge exists">exists</span></div>
</div>
<div class="box app">
<div class="label">🎁 Punchh</div>
<div class="detail">Loyalty platform</div>
<div class="detail"><span class="badge exists">future</span></div>
</div>
<div class="box app">
<div class="label">📱 App8</div>
<div class="detail">Mobile ordering</div>
<div class="detail"><span class="badge exists">future</span></div>
</div>
<div class="box app">
<div class="label">🌐 LSE Website</div>
<div class="detail">Marketing site</div>
<div class="detail"><span class="badge exists">future</span></div>
</div>
</div>
<!-- Arrow down: All redirect to Auth0 -->
<div class="v-arrow">
All apps redirect to Auth0 for login (Universal Login)
<div class="v-line">▼ ▼ ▼ ▼</div>
SSO: Auth0 session cookie skips re-auth across apps
</div>
<!-- Auth0 Tenant -->
<div class="row">
<div class="box auth0" style="min-width: 500px;">
<div class="label">🔐 Auth0 Tenant</div>
<div class="detail">dev-nx1ufn2l3q7v1wle.us.auth0.com</div>
<div class="detail" style="margin-top: 8px; color: var(--text);">
Session: 30d idle / 30d absolute &nbsp;•&nbsp; Refresh Token Rotation: ON
</div>
<div style="display: flex; gap: 12px; justify-content: center; margin-top: 12px; flex-wrap: wrap;">
<div style="background: rgba(251,146,60,0.15); padding: 6px 10px; border-radius: 6px; font-size: 0.75em;">
<strong>Post-Login Action</strong><br>Calls POST /resolve on identity-api
</div>
<div style="background: rgba(251,146,60,0.15); padding: 6px 10px; border-radius: 6px; font-size: 0.75em;">
<strong>Custom Claims</strong><br>Sets <code>identity_id</code> (UUID) on tokens
</div>
</div>
</div>
</div>
<!-- Arrow down: Post-Login Action calls identity-api -->
<div class="row" style="gap: 80px;">
<div class="v-arrow" style="flex: 1;">
<div>User tokens</div>
<div class="v-line">▼</div>
<div>audience: <code>api.luckystrike.cloud</code></div>
</div>
<div class="v-arrow" style="flex: 1;">
<div>M2M token (Post-Login Action)</div>
<div class="v-line">▼</div>
<div>audience: <code>identity.luckystrike.cloud</code></div>
</div>
</div>
<!-- Services row -->
<div class="row">
<div class="box app" style="flex: 1;">
<div class="label">⚡ Bowler API</div>
<div class="detail">api.luckystrike.cloud</div>
<div class="detail">Audience: <code>api.luckystrike.cloud</code></div>
<div class="detail"><span class="badge exists">exists</span></div>
</div>
<div class="arrow">
<div class="arrow-label">reads canonical user ID from JWT claim</div>
<div class="arrow-line">◄─────</div>
</div>
<div class="box identity" style="flex: 1;">
<div class="label">🪪 Identity API</div>
<div class="detail">identity.luckystrike.cloud</div>
<div class="detail">Audience: <code>identity.luckystrike.cloud</code></div>
<div class="detail"><span class="badge new">new</span></div>
</div>
</div>
<!-- Arrow down to DB -->
<div class="row" style="gap: 80px;">
<div class="v-arrow" style="flex: 1;">
<div class="v-line">▼</div>
<div>public schema</div>
</div>
<div class="v-arrow" style="flex: 1;">
<div class="v-line">▼</div>
<div>identity schema</div>
</div>
</div>
<!-- Database -->
<div class="row">
<div class="box db" style="min-width: 400px;">
<div class="label">🗄️ connected_play database</div>
<div style="display: flex; gap: 12px; justify-content: center; margin-top: 8px;">
<div style="background: rgba(52,211,153,0.15); padding: 6px 10px; border-radius: 6px; font-size: 0.75em;">
<strong>public.*</strong><br>customer, game, lane, etc.
</div>
<div style="background: rgba(56,189,248,0.15); padding: 6px 10px; border-radius: 6px; font-size: 0.75em;">
<strong>identity.users</strong><br>UUID PK, auth0_sub, email, phone
</div>
</div>
<div class="detail" style="margin-top: 8px;">Cross-schema JOINs for reads ✓</div>
</div>
</div>
</div>
</div>
<!-- AUTH0 CONFIGURATION -->
<h2>Auth0 Configuration</h2>
<h3>Auth0 APIs (Audiences)</h3>
<div class="config-grid">
<div class="config-item">
<div class="key">Bowler API <span class="badge exists">exists</span></div>
<div class="val"><code>https://api.luckystrike.cloud</code></div>
<div class="note">Used by bowler-connect and bowler-api. No changes needed.</div>
</div>
<div class="config-item">
<div class="key">Identity API <span class="badge new">new</span></div>
<div class="val"><code>https://identity.luckystrike.cloud</code></div>
<div class="note">New API for identity-api. Create in Auth0 Dashboard → APIs.</div>
</div>
</div>
<h3>Auth0 Applications</h3>
<div class="config-grid">
<div class="config-item">
<div class="key">Bowler Connect (Regular Web App) <span class="badge exists">exists</span></div>
<div class="val">Client ID: <code>P8bs1c...RVNob</code></div>
<div class="note">Callback: https://connect.luckystrike.cloud/auth/callback — No changes needed.</div>
</div>
<div class="config-item">
<div class="key">Identity Sync (M2M) <span class="badge m2m-badge">new · m2m</span></div>
<div class="val">Machine-to-Machine application</div>
<div class="note">Used by Post-Login Action to call identity-api. Authorized for <code>identity.luckystrike.cloud</code> audience.</div>
</div>
</div>
<h3>Tenant Settings</h3>
<div class="config-grid">
<div class="config-item">
<div class="key">Session Idle Timeout</div>
<div class="val">30 days (max)</div>
<div class="note">Settings → Sessions → Idle Session Lifetime</div>
</div>
<div class="config-item">
<div class="key">Session Absolute Timeout</div>
<div class="val">30 days (max)</div>
<div class="note">Settings → Sessions → Absolute Session Lifetime</div>
</div>
<div class="config-item">
<div class="key">Refresh Token Rotation</div>
<div class="val">Enabled</div>
<div class="note">Applications → Bowler Connect → Settings → Refresh Token Rotation</div>
</div>
<div class="config-item">
<div class="key">Persistent Sessions</div>
<div class="val">Enabled</div>
<div class="note">Settings → Sessions → Persistent Session</div>
</div>
</div>
<!-- TOKEN FLOWS -->
<h2>Token Flows</h2>
<div class="section">
<table class="token-table">
<thead>
<tr>
<th>Flow</th>
<th>Token Type</th>
<th>Audience</th>
<th>Used By</th>
</tr>
</thead>
<tbody>
<tr>
<td>User login → bowler-connect</td>
<td>Access Token (JWT)</td>
<td><code>api.luckystrike.cloud</code></td>
<td>bowler-api validates</td>
</tr>
<tr>
<td>Post-Login Action → identity-api</td>
<td>M2M Token (client credentials)</td>
<td><code>identity.luckystrike.cloud</code></td>
<td>identity-api validates</td>
</tr>
<tr>
<td>Custom claim on all user tokens</td>
<td>ID + Access Token claim</td>
<td>—</td>
<td><code>https://luckystrike.com/identity_id</code> = canonical UUID</td>
</tr>
</tbody>
</table>
</div>
<!-- LOGIN FLOW -->
<h2>Login Flow (Step by Step)</h2>
<div class="section">
<ol class="step-list">
<li>
<strong>User opens bowler-connect</strong> (or any LSE app) and clicks "Log In"
</li>
<li>
<strong>App redirects to Auth0</strong> Universal Login page. If the user has an existing Auth0 session cookie (e.g. from another LSE app), <strong>Auth0 skips the login prompt</strong> and issues tokens immediately.
</li>
<li>
<strong>Auth0 runs Post-Login Action</strong> which:<br>
&nbsp;&nbsp;a. Gets an M2M token via client credentials grant (<code>identity.luckystrike.cloud</code> audience)<br>
&nbsp;&nbsp;b. Calls <code>POST /resolve</code> on identity-api with the user's <code>sub</code>, email, phone<br>
&nbsp;&nbsp;c. Identity-api upserts <code>identity.users</code> and returns canonical UUID<br>
&nbsp;&nbsp;d. Action sets <code>identity_id</code> custom claim on ID + access tokens
</li>
<li>
<strong>Auth0 redirects back to app</strong> with tokens containing the canonical <code>identity_id</code>
</li>
<li>
<strong>App calls bowler-api</strong> (or its own backend) with the access token. Backend reads <code>identity_id</code> from the JWT — no extra call to identity-api needed for normal operations.
</li>
</ol>
</div>
<!-- WHAT TO CREATE -->
<h2>Action Items</h2>
<div class="section">
<ol class="step-list">
<li>
<strong>Create Auth0 API:</strong> <code>https://identity.luckystrike.cloud</code><br>
Dashboard → APIs → Create API
</li>
<li>
<strong>Create M2M Application:</strong> "Identity Sync"<br>
Dashboard → Applications → Create → Machine to Machine<br>
Authorize for the identity API audience
</li>
<li>
<strong>Set session lifetimes:</strong> 30d idle / 30d absolute<br>
Dashboard → Settings → Sessions
</li>
<li>
<strong>Enable refresh token rotation</strong> on Bowler Connect app<br>
Dashboard → Applications → Bowler Connect → Refresh Token Rotation
</li>
<li>
<strong>Create Post-Login Action:</strong> "Sync Identity"<br>
Dashboard → Actions → Flows → Login → Add Action<br>
Secrets: IDENTITY_API_URL, M2M_CLIENT_ID, M2M_CLIENT_SECRET, M2M_AUDIENCE
</li>
<li>
<strong>Update identity-api auth middleware</strong> to accept M2M tokens<br>
(accept both <code>api.luckystrike.cloud</code> and <code>identity.luckystrike.cloud</code> audiences)
</li>
<li>
<strong>Deploy identity-api</strong> and verify Post-Login Action fires on next login
</li>
</ol>
</div>
<p style="margin-top: 32px; color: var(--text-dim); font-size: 0.8em; text-align: center;">
LSE Identity Architecture — Option A (Separate Audience) • Connected Play • Jan 2026
</p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment