Choose an OAuth app type
The OAuth app type determines how your app authenticates when it exchanges an authorization code for an access token. Pick the type before you create the app, because you can’t change it afterwards:- Client-side: your app exchanges the code in a browser or a mobile or desktop app, where a secret wouldn’t stay secret. The app has no client secret and must use Proof Key for Code Exchange (PKCE).
- Server-side: your app exchanges the code from your backend, where you can keep a secret. Plain creates a client secret for the app and shows it once.
Create an OAuth app
Creating an OAuth app gives you a client ID, which identifies your app to Plain and isn’t secret. A server-side app also gets a client secret.1
Open the form
Go to Settings → OAuth applications and click Add OAuth app.
2
Fill in the form
The form has these fields:
- App name: The name users see when they sign in.
- Description: Optional.
- OAuth app type: Client-side or Server-side. The form selects Client-side by default.
- Redirect URLs: Where Plain sends a user after they sign in. Put each URL on its own line.
- Allowed origins: Shown only for a client-side app. See Allowed origins for a client-side app.
localhost.3
Create the app
Click Create OAuth app. For a client-side app, Plain opens the app’s settings page, which shows the client ID.For a server-side app, Plain first shows the client ID and client secret. Click Done to open the app’s settings page.
Allowed origins for a client-side app
An allowed origin is a website that may call Plain’s sign-in endpoints, such as the token endpoint, from the browser. A client-side app needs at least one, because it exchanges the code from the browser, and the browser blocks the call from any other origin. Write each origin on its own line as a scheme and host, with an optional port and no path, such ashttps://app.example.com or http://localhost:3000. A server-side app exchanges the code from your backend, so the create form doesn’t ask for allowed origins.
Get an access token
Your app gets an access token with the OAuth 2.0 authorization code flow: it sends the user to sign in, receives a code at a redirect URL, and exchanges that code for tokens. The authorization server ishttps://signin.auth.plain.com, and its OpenID configuration lists the endpoints.
1
Send the user to sign in
Redirect the user to the authorize endpoint with these query parameters:A user in more than one workspace chooses which workspace to sign in to. The app only works in the workspace you created it in.
client_id: Your app’s client ID.redirect_uri: One of the app’s redirect URLs.response_type:code.scope:openid, plusoffline_accessif you want a refresh token.stateandnonce: Random values you generate for this sign-in. Check that thestatePlain returns matches the one you sent.code_challengeandcode_challenge_method: The PKCE code challenge, with methodS256. A client-side app must send these. A server-side app can skip PKCE, but we recommend it.
2
Exchange the code for tokens
After the user signs in, Plain redirects to For a client-side app, send the code verifier and no client secret. The page’s origin must be one of the app’s allowed origins:The response includes these fields:
redirect_uri with a code. Exchange it at the token endpoint, https://signin.auth.plain.com/oauth2/token.For a server-side app, send the client secret from your backend. This request also sends the code verifier, because it used PKCE:access_token: The token you send to the API.expires_in: How many seconds the access token lasts.refresh_token: Included when the sign-in requestedoffline_access.
3
Refresh the access token
Before the access token expires, send the refresh token to the token endpoint with Store the refresh token from each response and use it for the next refresh.
grant_type=refresh_token and the client ID. A server-side app also sends its client secret:Call the API
The API URL and request body are the same as for an API key; see Key details. Send the access token in the same header an API key uses:401 and the error code UNAUTHENTICATED. The error doesn’t say which of these applies.

