Concepts
OAuth 2.0 with PKCE
CorrConnect speaks standard OAuth 2.0 authorization code with PKCE (RFC 6749 and RFC 7636), plus RFC 7009 token revocation. Most OAuth libraries work with it as a public client.
The flow
- Your app makes a
code_verifier: 43 to 128 characters fromA-Z a-z 0-9 - ., random each time._ ~ - It sends the athlete to
/oauth/authorizewithcode_, no padding, andchallenge = BASE64URL(SHA256(code_ verifier)) code_. Plain challenges are not accepted.challenge_ method=S256 - The athlete signs in and approves. The browser lands on your
redirect_uriwithcodeand yourstate. - Your app posts the code, the verifier and the same
redirect_urito/oauth/token. - It gets an access token and a refresh token.
CorrConnect checks the authorize request before it shows anything. A wrong client_id, an unregistered callback, an unknown scope, a state shorter than 8 characters or a malformed challenge stops there with a 400 error in the athlete's browser, and nothing is sent to your callback. If the athlete denies, your callback gets error=access_ and your state.
Codes
An authorization code:
- lasts 5 minutes,
- works once,
- only works for the app, callback, challenge and scopes it was issued for.
If a code is presented a second time, CorrConnect assumes someone copied it and withdraws every token the first exchange produced. Don't retry an exchange automatically; if you lost the response, start a new authorization.
Tokens
| Token | Prefix | Default lifetime | Notes |
|---|---|---|---|
| Access token | cca_ | 60 minutes | Send as Authorization: Bearer. Read expires_in from each response; the default can change. |
| Refresh token | ccr_ | 60 days | Works once. Each refresh returns a new pair. |
| Refresh chain | 365 days | Counted from the first code exchange. refresh_ counts down to whichever comes first. |
Both are opaque strings; don't parse them. CorrConnect stores only keyed hashes, so it can't show you a token again or tell you what one was. Keep them out of URLs, logs and analytics.
Treat refresh_token and refresh_ as optional in your parser. An app that ignores them keeps working: it sends the athlete through authorization again when the access token expires.
Refreshing
Post grant_, your client_id and the current refresh token. You get a new access token and a new refresh token. Save the new refresh token before you use the new access token; the old one is spent.
Refresh tokens are single use, and CorrConnect watches for a spent one coming back:
- Within 10 seconds of the first use (two of your workers raced), the second request gets its own new pair and the pair from the first request is withdrawn. Keep whichever response you saved last.
- After that, a spent refresh token is treated as stolen. CorrConnect ends the whole grant, sends
grant.revoked, and the athlete has to approve your app again.
The safe pattern is one refresh at a time per athlete: a lock or a queue keyed on the athlete.
You can ask for fewer scopes on refresh with scope=activities:read. Asking for a scope the grant doesn't hold fails with invalid_grant, the same answer as an expired or revoked token, so the error never tells anyone whether a token is still alive.
Token endpoint errors
/oauth/token uses the OAuth error shape instead of CorrConnect's usual one:
{"error": "invalid_grant", "error_description": "The authorization code is invalid, expired, used, or revoked.", "request_id": "req_..."}The possible values are invalid_request, invalid_client, invalid_grant, unsupported_ and, with 429, slow_down. See errors.
Revoking
POST /with the access token as a Bearer header ends the athlete's whole grant for your app. Use it when they disconnect inside your app. It works with an expired access token too.oauth/ revoke POST /releases one token and its partner (the access token and the refresh token issued together). The grant stays. It takes a Bearer header, or an RFC 7009 form body withoauth/ revoke-token token.
Both answer {"status": "revoked"} whether or not the token existed.
Expired access tokens are deleted 7 days after they expire, except the newest one for each active grant. That one is kept so an app without a refresh token can still end the grant with /oauth/revoke.
Using an OAuth library
Configure it as a public client with PKCE (S256), the authorization endpoint CORRCONNECT_, the token endpoint CORRCONNECT_, client authentication none (send client_id in the body), and scopes separated by spaces. CorrConnect publishes no OpenID Connect discovery document, ID tokens or userinfo endpoint; use GET /v1/profile for a stable athlete ID.