When should you use an OAuth app?
OAuth apps support the Authorization Code flow only, and PKCE is required. There is no
client_credentials flow for obtaining a token without a user, so use an API key for server-to-server integrations.
Step 1. Register an OAuth app
Organization apps are registered by an organization Admin or Developer, and workspace apps by a workspace Admin, at Tiro Platform.1
Open the OAuth apps screen
In the Tiro Platform side menu, open the [OAuth apps] menu for your organization or workspace and press [Register OAuth app].
2
Enter the app details
Enter the app name, the Redirect URI, and the scopes the app will request from users. The Redirect URI must start with
https://, and it won’t register if the URL carries user info or a fragment after #. It has to match the URL you return to after authorization character for character, and it can’t be edited after registration, so register a new app if the URL changes.3
Store the Client ID and Client Secret
Right after registration, the screen shows the Client ID and Client Secret. The Client Secret is shown this one time and can’t be retrieved again. Store it somewhere safe, such as a server environment variable, right away.
http://localhost can’t be registered as a Redirect URI. For local development, register an HTTPS tunnel or a development domain as the Redirect URI and let that address forward the callback to your machine.
The OAuth app registration screen is rolling out gradually. If you don’t see the menu, ask your account manager or partners@theplato.io and we’ll issue an app for you.
Scopes you can request
An OAuth app can request only the permissions a user is able to delegate. Organization management scopes (organization_member:*, workspace:*, session:write) can’t be delegated and are available to organization API keys only.
What the app actually reaches is the overlap between the scopes the user consented to and the permissions that user holds in Tiro. Workspaces and notes the user can’t see stay out of reach no matter which scopes were granted.
Step 2. Get user consent
Send the user from your app to the Tiro authorization page. First generate a randomcode_verifier, then hash that string as UTF-8 bytes with SHA-256 and send the unpadded Base64URL encoding of the digest as code_challenge. Sending a hex string or the raw digest bytes gets rejected at the token exchange step.
After the user signs in with their Tiro account, reviews the app name and requested scopes on the consent screen, and approves,
code and state come back to your Redirect URI.
Validate state on the callback
Checkstate before you exchange the token. Store the value you generated for the authorize request in the user’s session, compare it with the value that comes back on the callback, then clear the stored value. If it’s missing or different, stop the request right there.
Skipping this check allows login CSRF: an authorization the attacker started gets attached to the victim’s session, connecting them to an account they know nothing about.
Members of organizations using SSO sign in through their corporate IdP as usual before the consent screen. The consent screen is scoped to that member’s Tiro account, and the app receives that account’s permissions only.
Step 3. Exchange the code for a token
Turn the validatedcode into an access token from your server. Handle the Client Secret on the server only and never put it in browser or app code. An authorization code works exactly once, so sending it twice is rejected with invalid_grant.
client_id, client_secret).
Step 4. Call the API
Put the access token in theAuthorization header exactly as you would an API key. The endpoints you can call and the response formats match what’s covered in the API Overview.
Step 5. Refresh the token
If the token exchange response includes arefresh_token, you can get a new access token without asking the user to consent again.
- A refresh token works exactly once. When the response carries a new refresh token, discard the previous value and store the new one.
- Sending a refresh token that was already used is treated as theft, and every token on that user connection is invalidated. You then have to ask the user to consent again.
- A connection left unrefreshed for a long time expires. Each refresh pushes the expiry further out, so connections in steady use stay alive.
- Refreshes are rejected once the app has been discarded or the user has disconnected it.
Disconnect
When a user stops using the app, revoke the refresh token to disconnect. Access tokens already issued expire on their own onceexpires_in passes.
Server metadata
The endpoints and supported methods are published in the standard metadata document.Common errors
Related pages: Authentication · Integrate at the organization level · API Overview