· SetoTower · Developers · 8 min read
Sign in with Canvas: the complete OAuth guide
Add SetoTower identity to your website, mobile app, or API with Canvas OAuth 2.0, then manage every app manually or through the management API.
Building the interesting part of an app is already enough work. Rebuilding sign-in, password recovery, account identity, and authorization plumbing for every new project is the sort of repetition that makes a weekend project quietly become a next-month project.
Canvas OAuth lets your users sign in with the permanent identity they already use across SetoTower. The service is free, uses standard OAuth 2.0 Authorization Code flow, supports PKCE for browser and mobile apps, and can return a small, predictable profile instead of a suitcase full of private account data.
You can register and maintain apps from Canvas > Account > OAuth Apps. If you operate several services or deploy automatically, the same page can issue a narrowly scoped management API token so your tooling can create and manage apps for you.
This guide covers both paths from beginning to end.
What your app receives
Canvas exposes two user scopes:
profilereturns the permanent Canvas username, display name, first and last name, and avatar URL.emailadditionally returns the email address and whether Canvas has verified it.
The permanent username is the best stable account key for displaying a SetoTower identity. The sub field currently contains that same username. Store sub as an opaque string rather than trying to extract meaning from it; that habit will save future-you from an unnecessarily dramatic database migration.
Canvas does not give customer apps access to invoices, services, support tickets, passwords, recovery addresses, Discord metadata, or administrator status.
1. Register an app in Canvas
Sign in to Canvas, open Account, and select OAuth Apps. Choose New app, then enter:
- A name your users will recognize in Canvas and your login handoff.
- The application type.
- One or more exact redirect URLs, one per line.
Choose Web app when your server can keep a client secret private. Choose Browser or mobile for a single-page app, desktop app, mobile app, command-line tool, or anything distributed to users. Public apps do not receive a secret and must use PKCE.
Production redirect URLs must use HTTPS. Plain HTTP is accepted only for loopback and localhost development. Wildcards, URL fragments, embedded usernames or passwords, and approximate matches are rejected. If you register https://example.com/auth/callback, Canvas will not redirect to https://example.com/auth/callback/ with an extra slash. OAuth is fussy here for a good reason.
An account can keep up to 10 active OAuth apps, and each app can have up to five redirect URLs.
Canvas shows a confidential client secret exactly once. Put it in a server-side secret manager or protected environment variable immediately. Never include it in browser JavaScript, a mobile bundle, a Git repository, a screenshot, or the group chat where someone will definitely search for it six months later.
2. Send the user to Canvas
Direct the browser to the authorization endpoint:
https://canvas.setotower.com/oauth/authorizeA confidential web app can begin with a URL like this:
https://canvas.setotower.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback
&response_type=code
&scope=profile%20email
&state=RANDOM_UNGUESSABLE_VALUEGenerate a new random state value for every attempt, save it in the user’s server-side session, and compare it exactly when the user returns. Reject the callback if it is missing or different. State protects the login flow from request forgery; it is not decorative confetti for the query string.
Clicking Sign in with Canvas is the user’s authorization. When the user already has a Canvas session, Canvas returns to the registered redirect URL immediately with no second confirmation screen. When they are signed out, Canvas asks them to sign in and then resumes the same request automatically:
https://example.com/auth/callback?code=AUTHORIZATION_CODE&state=RANDOM_UNGUESSABLE_VALUEAuthorization codes are short-lived and single-use.
3. Use PKCE for public apps
Public clients must create a high-entropy code_verifier for each login. Hash it with SHA-256, encode that hash using URL-safe Base64 without padding, and send the result as code_challenge.
https://canvas.setotower.com/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=http%3A%2F%2Flocalhost%3A4173%2Fauth%2Fcallback
&response_type=code
&scope=profile%20email
&state=RANDOM_UNGUESSABLE_VALUE
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256Keep the original verifier locally until the callback. You will send it during the token exchange. Do not reuse a verifier for later sign-ins.
Most maintained OAuth client libraries already implement state and PKCE. Use one when your framework offers it. Authentication code is a poor place to celebrate writing every primitive by hand.
4. Exchange the code for tokens
Send a form-encoded POST request to:
https://canvas.setotower.com/oauth/tokenFor a confidential web app:
curl --request POST 'https://canvas.setotower.com/oauth/token' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
--data-urlencode 'redirect_uri=https://example.com/auth/callback' \
--data-urlencode 'code=AUTHORIZATION_CODE'For a public PKCE app, omit client_secret and add the original verifier:
curl --request POST 'https://canvas.setotower.com/oauth/token' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'redirect_uri=http://localhost:4173/auth/callback' \
--data-urlencode 'code=AUTHORIZATION_CODE' \
--data-urlencode 'code_verifier=ORIGINAL_RANDOM_VERIFIER'A successful response contains an access token, refresh token, token type, and lifetime:
{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "...",
"refresh_token": "..."
}Treat both tokens like passwords. Confidential apps should keep them in encrypted server-side storage. Browser and mobile apps should use the strongest protected storage their platform provides.
5. Read the user’s identity
Call the profile endpoint with the access token:
curl 'https://canvas.setotower.com/oauth/user' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer ACCESS_TOKEN'With profile email, the response looks like:
{
"sub": "maya.dev",
"username": "maya.dev",
"name": "Maya Developer",
"given_name": "Maya",
"family_name": "Developer",
"picture": "https://www.gravatar.com/avatar/...",
"email": "[email protected]",
"email_verified": true
}If the app requested only profile, the email fields are absent. Code for their absence instead of assuming they contain null.
The token and profile endpoints support cross-origin requests for public PKCE clients. The authorization endpoint is still a browser navigation, not a background API call.
6. Refresh an expired access token
Exchange the refresh token at the same token endpoint:
curl --request POST 'https://canvas.setotower.com/oauth/token' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=YOUR_REFRESH_TOKEN' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'client_secret=YOUR_CLIENT_SECRET'Public clients omit the client secret. Save the new refresh token returned by Canvas; token rotation means the old value may no longer be the one you should keep.
Managing apps in Canvas
The OAuth Apps page lists only apps owned by your account. From there you can:
- copy a client ID;
- edit the name and exact redirect URLs;
- rotate a confidential client secret;
- see how many active access tokens an app has; and
- revoke the app and every access token it issued.
Rotating a secret invalidates the previous secret immediately. Revoking an app cannot be undone. Both are useful buttons, but neither improves when clicked experimentally in production.
Automating app management with the API
Open Management API on the same OAuth Apps page and choose New API token. Name the token after the system that will use it, such as Production deploy or Internal platform. Canvas shows the bearer token once and keeps only its authorization record afterward.
Management tokens carry one permission: oauth.apps.manage. They can operate on OAuth apps owned by the same Canvas user. They cannot read profile data, invoices, services, tickets, or other customer accounts. An account can keep up to five active management tokens, and each token expires after one year.
Keep management tokens in server-side secret storage. Do not put one in frontend code. If a token is exposed or a deployment no longer needs it, revoke it from Canvas and create a replacement.
Every management request begins at:
https://canvas.setotower.com/api/v1/oauth/appsInclude these headers:
Accept: application/json
Authorization: Bearer YOUR_MANAGEMENT_TOKENList apps
curl 'https://canvas.setotower.com/api/v1/oauth/apps' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN'Create an app
curl --request POST 'https://canvas.setotower.com/api/v1/oauth/apps' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN' \
--data '{
"name": "Docs website",
"application_type": "confidential",
"redirect_uris": [
"https://docs.example.com/auth/callback",
"http://localhost:3000/auth/callback"
]
}'Use public as application_type for a PKCE client. A confidential app’s successful 201 Created response includes client_secret once. A public app returns client_secret: null.
Read one app
curl 'https://canvas.setotower.com/api/v1/oauth/apps/CLIENT_ID' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN'Update an app
Send either field or both:
curl --request PATCH 'https://canvas.setotower.com/api/v1/oauth/apps/CLIENT_ID' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN' \
--data '{
"name": "Docs and status",
"redirect_uris": ["https://docs.example.com/oauth/callback"]
}'Application type is intentionally immutable. Register a new app when moving between a confidential architecture and a public PKCE client; that makes the security change explicit.
Rotate a client secret
curl --request POST 'https://canvas.setotower.com/api/v1/oauth/apps/CLIENT_ID/secret' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN'The new client_secret appears once in the response. This endpoint returns a validation error for public clients because they have no secret to rotate.
Revoke an app
curl --request DELETE 'https://canvas.setotower.com/api/v1/oauth/apps/CLIENT_ID' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer YOUR_MANAGEMENT_TOKEN'Success returns 204 No Content and revokes the app’s issued tokens.
The API returns 401 for a missing or invalid bearer token, 403 when the token lacks the management scope, 404 when an app is not owned by the token’s user, 422 for invalid input, and 429 when a caller exceeds a safety limit. Ownership failures deliberately look like missing records; the API will not confirm another user’s client IDs.
A short production checklist
Before calling the integration finished:
- Use HTTPS everywhere outside local development.
- Generate and verify a fresh state value on every authorization attempt.
- Use S256 PKCE for every public client.
- Keep client secrets, access tokens, refresh tokens, and management tokens out of logs and source control.
- Request only the scopes your app actually needs.
- Match redirect URLs exactly.
- Handle authorization errors and expired codes without showing users a mysterious blank page.
- Rotate leaked credentials and revoke apps that are no longer used.
- Test login in a private browser session before announcing that authentication is “basically done.”
That is the full system: Canvas owns the durable identity and standards-heavy authentication machinery; your app keeps its own local session and gets back to being the thing you wanted to build.


