Apps: Sign in with The Hub
An app is your website or program, registered in the dashboard. People can sign in to it with their The Hub account, and it can see what they allow: who they are, and which spaces they're in. It never sees their password, and it can't read their messages or act as them.
The Hub uses OAuth 2.0 with the authorization code flow. Apps that run where they can't keep a secret (a single-page website, a desktop or mobile app) use PKCE instead of the secret.
Register your app
In the dashboard → Apps → New app:
- Name and description: shown to people when they're asked to allow your app.
- Redirect URLs: where The Hub sends people back after they answer. Use
https://addresses, orhttp://localhostwhile you develop. The redirect in each request must match one of them exactly.
You get a client ID (hub_…, public) and a client secret (vls_…, shown once). Keep the secret on your server. Reset secret makes a new one and switches the old one off.
Scopes
| Scope | What your app can see |
|---|---|
identify | The person's user ID, username, display name, avatar, colour and when they joined. |
spaces | The names and icons of the spaces they're in, and which ones they own. |
Ask for the scopes you need, separated by spaces. identify is used when you ask for none.
The flow
1. Send the person to The Hub
https://the-hub.coffee/app/oauth.html
?client_id=hub_…
&redirect_uri=https://example.com/callback
&scope=identify%20spaces
&state=<a random value you keep>
With PKCE, also send code_challenge (the base64url SHA-256 of a random code_verifier you keep) and code_challenge_method=S256.
They see your app's name, who made it and what it asks for, and choose Allow or Cancel.
2. They come back
- Allowed:
https://example.com/callback?code=…&state=… - Cancelled:
https://example.com/callback?error=access_denied&state=…
Check that state is the value you sent. The code works once, for 10 minutes.
3. Trade the code for tokens
POST https://the-hub.coffee/app/api.php?action=oauth_token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=…&redirect_uri=https://example.com/callback
&client_id=hub_…&client_secret=vls_…
With PKCE, send code_verifier=… instead of client_secret. The answer:
{
"access_token": "vlo_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "vlr_…",
"scope": "identify spaces"
}
Errors use OAuth's format: {"error": "invalid_grant", "error_description": "…"}.
4. Call the API
GET https://the-hub.coffee/app/api.php?action=oauth_me
Authorization: Bearer vlo_…
{"ok": true, "user": {"id": "3f74ea79a172d033a402f95497", "username": "bob", "display_name": "Bob",
"avatar": "uploads/avatars/….png", "color": "#faa61a", "created_at": 1791336070538}}
oauth_spaces (scope spaces) answers {"ok": true, "spaces": [{"id": 1, "name": "…", "icon": "…", "owner": false}]}. Pictures are paths on https://the-hub.coffee/app/. Use the id to recognise someone: it never changes, unlike their username.
5. Refresh
An access token lasts an hour. Get a new pair with the refresh token (it changes every time, so keep the new one):
POST …?action=oauth_token
grant_type=refresh_token&refresh_token=vlr_…&client_id=hub_…&client_secret=vls_…
When people remove access
Anyone can remove an app in Settings → Data and privacy → Apps you signed in to. Its tokens stop working at once, and refreshing answers invalid_grant: send them through step 1 again.
CORS
oauth_token, oauth_me and oauth_spaces answer requests from any website (no cookies are involved), so a single-page app with PKCE can call them straight from the browser.