TheoremDocs Open Theorem
Sections

Every claim of the token Theorem signs for your app, how to check it, where the keys are, how long it lasts, and why aud must be checked.

Every request Theorem sends your app carries Authorization: Bearer and a token: a JWT that Theorem signed for one person and your one app. Check it on every request, before anything else. Refuse the request with 401 if any check fails.

The claims

{ "iss": "https://theorem.sa",
  "sub": "acc_01KA0A3C4D5E6F7G8H9JKMNPQR",
  "aud": "app_01KA3B3C4D5E6F7G8H9JKMNPQR",
  "email": "omar@example.com",
  "name": "Omar Al-Fahad",
  "team": { "id": "team_01KA2B3C4D5E6F7G8H9JKMNPQR", "name": "Al-Noor School",
            "leader": false,
            "roles": [ { "id": "rol_01KA2F3C4D5E6F7G8H9JKMNPQR", "name": "Finance" } ] },
  "iat": 1800000000, "exp": 1800000300 }
ClaimWhat it is
issWho signed it: always https://theorem.sa.
subThe person's account ID, acc_ and 26 letters and digits. It never changes: key your data by it.
audYour app's ID, app_ and 26 letters and digits, as the Connections tab shows it.
emailThe address the person signs in to Theorem with. It is not their address on the team's domain.
nameThe person's name. Left out for a person who has given none.
teamYour app's team, as it is now.
team.idThe team's ID, team_ and 26 letters and digits.
team.nameThe team's name. A leader may change it.
team.leadertrue when the person leads the team.
team.rolesThe team's roles the person holds, each { "id", "name" }, in the team's order. [] for none.
iatWhen it was signed, in seconds since 1970.
expWhen it ends: iat and 300 seconds.

There is nothing else. The token says nothing of the person's other teams, their session or their chat.

Checking it

  1. Read the header. alg is EdDSA, and kid names the key it was signed with.
  2. Get Theorem's public keys from https://theorem.sa/api/identity/jwks. It is a JSON Web Key Set of Ed25519 keys ("kty": "OKP", "crv": "Ed25519"). Keep them, and read them again when a token names a kid you don't have. If you saved them to a file, save them again then.
  3. Check the signature with the key of that kid. Accept EdDSA only.
  4. Check iss is https://theorem.sa.
  5. Check aud is your own app's ID.
  6. Check exp has not passed.

Most JWT libraries do all of it when you give them the keys, the issuer and the audience. In Python with PyJWT, as the example does:

keys = jwt.PyJWKClient("https://theorem.sa/api/identity/jwks")
key = keys.get_signing_key_from_jwt(token)
claims = jwt.decode(token, key.key, algorithms=["EdDSA"],
                    audience=APP_ID, issuer="https://theorem.sa")

Why aud must be checked

Your app's ID is not a secret: your team's leaders see it. And another team can add your app's address as its own app, so Theorem would send your app tokens for that team's people, with that team's role names, Finance among them. Only aud tells those tokens apart from your own team's: it is that other app's ID, not yours. An app that skips the check lets in anyone from any team.

How long it lasts

A token lasts 300 seconds, five minutes. Theorem may send the same token for several calls until shortly before it ends, then makes a new one.

A person removed from the team, or from the role that gave them your app, gets no new token. So the most they can still reach your app is until their last token ends: five minutes at most. If that is too long for something, check with your own records too.

Its size

A token names the person's roles, so it can be long: up to about 7,200 bytes for someone holding 20 roles with 40-letter names. Make sure your server and any proxy in front of it accept a header that size.

What to answer when a check fails

Answer 401, with WWW-Authenticate: Bearer. A leader who checks the app, and anyone who uses it, then sees that your app refused Theorem's token. Say nothing about which check failed to the caller; write it to your own log instead, as the example does.