Sblurp

Sign-in and tokens

sblurp.chat is an OAuth 2.0 authorization server. It signs people in and gives the app a token for each community server. Community servers check those tokens with sblurp.chat's public keys. Bots use their own tokens instead.

The flow

  1. The app sends the person to sign in once, with Authorization Code and PKCE:

    GET https://sblurp.chat/oauth/authorize?response_type=code&client_id=sblurp-web
        &redirect_uri=...&code_challenge=...&code_challenge_method=S256&state=...
    
  2. sblurp.chat redirects back with code, state and iss. The app trades the code:

    POST https://sblurp.chat/oauth/token
    grant_type=authorization_code&client_id=sblurp-web&code=...&redirect_uri=...&code_verifier=...
    

    The reply has a refresh_token and an access_token for sblurp.chat itself.

  3. For each community server, the app asks for a token with that server as the audience:

    POST https://sblurp.chat/oauth/token
    grant_type=refresh_token&client_id=sblurp-web&refresh_token=...&audience=https://chat.example.com
    

    The reply has a new refresh token and an access token for that server only.

  4. The app sends the access token to the server as Authorization: Bearer <token>.

The only client is the web app, sblurp-web, with fixed return addresses. PKCE with S256 is required. A code works once, within 60 seconds.

Token reply

{"access_token": "eyJ...", "token_type": "Bearer", "expires_in": 900, "refresh_token": "..."}

Access tokens

An access token is a JWT signed with EdDSA (Ed25519). It lasts 15 minutes. Its claims:

Claim What
iss The issuer, https://sblurp.chat.
sub The account id. Stable.
aud The server's base URL, or the issuer for sblurp.chat's own API.
exp, iat Expiry and issue time, in Unix seconds.
jti A unique id.
preferred_username The username.
email_verified Whether the email address is confirmed.
age_over_13, age_over_18 What the person declared at sign-up.

The token carries no profile. The app sends the profile in Join, after the person agrees.

Refresh tokens

  • Opaque, and good for 60 days.
  • Rotated on every use: each refresh returns a new one.
  • Sending the same token again within 10 seconds is treated as a retry and gets the same new token.
  • A used token that comes back later means someone else has a copy. sblurp.chat then revokes every token in that chain.

Revoke a refresh token, and its whole chain, with RFC 7009:

POST https://sblurp.chat/oauth/revoke
token=...

It always answers 200.

How a server checks a token

  1. The iss must be in the server's trusted issuers. It checks this before fetching any keys.
  2. It fetches {iss}/.well-known/jwks.json and caches the keys for an hour. An unknown kid makes it fetch again, at most every 30 seconds.
  3. It checks the signature (only alg: EdDSA), then aud against its own base URL, then exp, iat and nbf, with 60 seconds of clock skew.

A token for one community is useless at another. A server knows a person by (iss, sub), never by sub alone.

sblurp.chat endpoints

Endpoint What
GET /oauth/authorize Starts sign-in.
POST /oauth/token authorization_code and refresh_token grants.
POST /oauth/revoke Revokes a refresh token chain.
GET /api/me The account behind an access token for sblurp.chat: id, username, email_verified, display_name, avatar_url, bio, pronouns.
GET /.well-known/jwks.json Public signing keys.
GET /.well-known/openid-configuration Discovery document.

Errors are JSON, such as {"error": "invalid_grant", "error_description": "code expired"}. The codes are invalid_client, invalid_grant, invalid_target (a bad audience), unsupported_grant_type and invalid_token.

Bot tokens

A bot token starts with sbb_. An admin makes it with BotService.CreateBot, and it is shown once. The server stores only its SHA-256 hash. It works only on the server that made it. See Bot tokens.

Tools and agents can read every call and type from schema.json.