OAuth 2.0: Login with TalonTodo
This guide describes the authorization code flow for third-party applications.
1. Register an OAuth client
- Open the TalonTodo web app and go to Settings, then API.
- Create an application: name, optional description, and redirect URIs (exact URLs allowed for callbacks).
- Save the client ID and client secret immediately (the secret is shown only once).
2. Authorization endpoint (user consent)
Send the user's browser to the TalonTodo authorize URL (web app), with query parameters:
| Parameter | Value |
|---|---|
| response_type | code |
| client_id | Your public client ID |
| redirect_uri | One of the registered redirect URIs (must match exactly) |
| scope | Space-separated scopes (optional; defaults to all allowed scopes) |
| state | Random string for CSRF protection (recommended) |
Example (replace APP with your app origin, for example
https://app.talontodo.com):https://APP/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=https%3A%2F%2Fmyapp.com%2Fcallback&scope=tasks%3Aread%20tasks%3Awrite&state=RANDOM_STATEThe user signs in (if needed) and approves or denies access. On approval, the backend returns JSON to the frontend with a redirectUrl your app should navigate to, or you follow the redirect that includes:
https://myapp.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATEOn denial:
https://myapp.com/callback?error=access_denied&state=RANDOM_STATE3. Token endpoint
POST
https://API/api/oauth/tokenUse Content-Type: application/json or application/x-www-form-urlencoded.
Authorization code exchange
| Field | Value |
|---|---|
| grant_type | authorization_code |
| client_id | Your client ID |
| client_secret | Your client secret |
| code | The code from the redirect |
| redirect_uri | Same redirect URI used in the authorize request |
Response (200):
{
"access_token": "...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "tasks:read tasks:write"
}Access tokens expire in 1 hour. Refresh tokens expire in 30 days.
Refresh token
| Field | Value |
|---|---|
| grant_type | refresh_token |
| client_id | Your client ID |
| client_secret | Your client secret |
| refresh_token | The refresh token |
Each refresh rotates the refresh token (old one is invalidated).
4. Revoke a token
POST
https://API/api/oauth/revoke{ "token": "ACCESS_OR_REFRESH_TOKEN" }5. Calling APIs
Send the access token on each request:
Authorization: Bearer ACCESS_TOKENSee the REST API reference for
/api/v1/* routes.Example: Node (fetch)
const tokenRes = await fetch('https://api.example.com/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: process.env.TALON_CLIENT_ID,
client_secret: process.env.TALON_CLIENT_SECRET,
code,
redirect_uri: 'https://myapp.com/callback',
}),
});
const tokens = await tokenRes.json();
const tasksRes = await fetch('https://api.example.com/api/v1/tasks', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});Security notes
- Keep client_secret server-side only.
- Always validate state on callback.
- Use HTTPS in production.
- Tokens in browser URLs or iframes can leak via Referer; prefer server-side token exchange and short-lived tokens for widgets.