Read the SSO user info
Your app never needs to build login itself. How to read who is connected right now, via the SDK or the SSO headers.
An app behind AxHub never needs to build login itself.
Read who just arrived, and show each person their own screens and data.
The principle fits in one line. The gate standing in front of your app checks login on your behalf, and only on requests that pass does it attach six tags saying "this is who the person is" before handing the request to your app. These tags are called the SSO headers.
If you built from a template
The Next.js and Astro templates already read it through the SDK.
import { makeAxhub } from '@/lib/axhub-server';
const sdk = await makeAxhub();
const me = await sdk.identity.me();
// me.email, me.name, me.tenants (companies and roles)Key your data on something stable per person, like me.email. That's how the template's to-do list separates data per user.
const userKey = me?.email ?? 'local-dev';
await db()`INSERT INTO todos (user_key, title) VALUES (${userKey}, ${title})`;Filter reads by WHERE user_key = ${userKey} as well, so nobody's rows leak into someone else's screen.
Reading from the headers
You can skip the SDK and read the headers directly. Here are the six that arrive, and what each carries.
| Header | Value |
|---|---|
X-AxHub-User-ID | The user's UUID |
X-AxHub-User-Email | base64-encoded email |
X-AxHub-User-Name | base64-encoded name |
X-AxHub-App-Role | owner · platform_admin · tenant_admin · app_member · tenant_member · guest |
X-AxHub-Is-Admin | true · false |
X-AxHub-Tenant-Slug | The tenant slug |
On the server, you read them like this.
export function currentAxHubUser(req: Request) {
const h = req.headers;
return {
id: h.get('x-axhub-user-id'),
email: Buffer.from(h.get('x-axhub-user-email') ?? '', 'base64').toString('utf8'),
name: Buffer.from(h.get('x-axhub-user-name') ?? '', 'base64').toString('utf8'),
role: h.get('x-axhub-app-role'),
isAdmin: h.get('x-axhub-is-admin') === 'true',
tenant: h.get('x-axhub-tenant-slug'),
};
}Always read them on the server. Any X-AxHub-* value sent from outside your app is overwritten by the gate — whether it came from a browser, a terminal command, or a script.
When the headers are empty
If X-AxHub-User-ID is empty, treat the visitor as not signed in. That happens in two cases.
- The request was denied — all six headers arrive empty
- An anonymous visitor reached an externally public app — they pass through, but all six are still empty
Branching on the role
guest means someone who is signed in but isn't a member of this app. That's what a person from another company gets on an externally public app.
If a value arrives that isn't in the table, treat it as the lowest privilege. Roles can be added later, and treating an unknown value as an admin is how holes appear.
Reading via the SDK
The SDK can tell you who's here too. On top of email and name, you get their companies and roles in one call.
const me = await sdk.identity.me();
// me.email, me.name, me.tenants (companies and roles)You've succeeded when
You sign in with your company account, visit the deployed app, and the email and name your app read show your own account.
Next, send your app's news to people at your company in Send notifications.