Skip to content

Commit e2abe17

Browse files
committed
feat(api): add /api/userinfo handler in server.tsx
Adds a /api/userinfo GET handler to the server entry's path-match chain. Mirrors the other 7 examples; sits next to the existing /api/auth/* and /api/(un)?protected blocks. TanStack Start doesn't have a file-based API route convention so this lives inline.
1 parent 84e30d9 commit e2abe17

2 files changed

Lines changed: 36 additions & 7 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
{
22
"name": "Zitadel Example TanStack Start Auth",
33
"image": "mcr.microsoft.com/devcontainers/javascript-node:22-bookworm",
4-
"forwardPorts": [
5-
3000
6-
],
4+
"forwardPorts": [3000],
75
"portsAttributes": {
86
"3000": {
97
"label": "Application",
@@ -18,10 +16,7 @@
1816
"postCreateCommand": "npx playwright install --with-deps chromium",
1917
"customizations": {
2018
"vscode": {
21-
"extensions": [
22-
"dbaeumer.vscode-eslint",
23-
"esbenp.prettier-vscode"
24-
]
19+
"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
2520
}
2621
}
2722
}

app/server.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,40 @@ async function handleRequest(request: Request): Promise<Response> {
147147
return withCookiesFixed(request, await handlers.POST({ request }));
148148
}
149149

150+
// GET /api/userinfo → proxy to Zitadel's OIDC userinfo endpoint using the
151+
// user's access token. Demonstrates how to call Zitadel APIs from the app
152+
// server. The session's JWT carries a login-time snapshot; this hits the
153+
// live endpoint so callers see current roles, metadata, etc.
154+
if (pathname === '/api/userinfo') {
155+
if (request.method !== 'GET') {
156+
return new Response(null, { status: 405, headers: { Allow: 'GET' } });
157+
}
158+
const session = await getSession(request);
159+
if (!session?.accessToken) {
160+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
161+
}
162+
try {
163+
const upstream = await fetch(
164+
`${process.env.ZITADEL_DOMAIN}/oidc/v1/userinfo`,
165+
{
166+
headers: { Authorization: `Bearer ${session.accessToken}` },
167+
},
168+
);
169+
if (!upstream.ok) {
170+
// noinspection ExceptionCaughtLocallyJS
171+
throw new Error(`UserInfo API error: ${upstream.status}`);
172+
}
173+
const userInfo = await upstream.json();
174+
return Response.json(userInfo);
175+
} catch (error) {
176+
console.error('UserInfo fetch failed:', error);
177+
return Response.json(
178+
{ error: 'Failed to fetch user info' },
179+
{ status: 500 },
180+
);
181+
}
182+
}
183+
150184
// Public API endpoint — no authentication required.
151185
if (pathname === '/api/unprotected') {
152186
return Response.json({ ok: true });

0 commit comments

Comments
 (0)