ConnectTokens

Tokens

Three token types are returned by /v1/oauth/token. They serve different jobs.

Access token

Purpose: authorize a single API call. Send it as Authorization: Bearer <token> on every request to a Loop service.

Format: opaque, prefixed with lph_at_. Not a JWT — services validate by calling identity’s introspection endpoint or by checking the token against a fast cache (Redis). This means we can revoke a token instantly; we don’t have to wait for the JWT to expire.

Lifetime: 1 hour by default. Configurable per-app down to 5 minutes for high-sensitivity integrations.

Cardinality: unlimited per (user, client). The same user can have many access tokens active for the same app.

Refresh token

Purpose: obtain a new access token without sending the user through the consent flow again.

Format: opaque, prefixed with lph_rt_.

Lifetime: 90 days from last use. Each refresh extends the window.

Rotation: every successful refresh issues a new refresh token AND invalidates the previous one. If you ever see the same refresh token used twice, that’s a leak — Loop will revoke the entire grant for the user × client and alert the user.

Cardinality: one active refresh token per (user, client). Re-running the authorization flow replaces it.

ID token

Purpose: OIDC — prove who the user is to your app.

Format: JWT, signed by Loop’s identity key. Public keys available at https://identity.platform.loop.health/.well-known/jwks.json.

Lifetime: 5 minutes. Don’t store; use it once to identify the user, then rely on the access token + introspection for ongoing calls.

Claims:

ClaimMeaning
subthe user’s stable Loop ID. Never reused, never recycled.
issalways https://identity.platform.loop.health
audyour client_id
emailonly if email scope was granted
email_verifiedalways true if email is present
nameonly if profile scope was granted
brandthe brand context for this session (loop, loopbio, …)
iat, exp, noncestandard

Introspection

Services use POST /v1/oauth/introspect to check that an access token is still valid:

POST https://identity.platform.loop.health/v1/oauth/introspect
Authorization: Basic <base64(client_id:client_secret)>

token=lph_at_...

Response:

{
  "active": true,
  "sub": "user_01HXY...",
  "client_id": "client_01HXY...",
  "scope": "read:biomarkers read:protocols",
  "exp": 1735689600,
  "brand": "loop"
}

If active: false, the service returns 401 to the original caller. Don’t cache introspection responses beyond exp.

Revocation

A user can revoke a grant from their Connected Apps page. Apps can revoke their own tokens by calling:

POST https://identity.platform.loop.health/v1/oauth/revoke
Authorization: Basic <base64(client_id:client_secret)>

token=<access_or_refresh_token>
&token_type_hint=access_token   # or refresh_token

A 200 means “this token is no longer valid” — even if it was already invalid. No further confirmation needed.

Revoking a refresh token revokes all associated access tokens. Revoking an access token only revokes that one.

What tokens are never used for

  • Server-to-server inside the platform. Use client_credentials (M2M) for that. See services/identity M2M docs.
  • Long-term machine integrations without a user. Same — M2M.
  • Webhook signature verification. Webhooks use a separate signing secret per app, not the user’s tokens.