Notion
Sign in with Notion using OAuth 2.0.
Get credentials
Create an integration
Go to Notion Integrations and click New integration. Set the type to Public — this is required for OAuth with external users.
Configure OAuth settings
In the integration settings, scroll to OAuth Domain & URIs. Add your redirect URI:
https://auth.example.com/auth/oauth/notion/callbackSet Redirect URIs and save.
Copy credentials
Under Basic Information, copy the OAuth client ID and generate an OAuth client secret.
Configuration
import { createKavach } from 'kavachos';
import { oauth } from 'kavachos/plugins/oauth';
const kavach = await createKavach({
database: { provider: 'postgres', url: process.env.DATABASE_URL! },
secret: process.env.KAVACH_SECRET!,
baseUrl: 'https://auth.example.com',
plugins: [
oauth({
providers: [
{
id: 'notion',
clientId: process.env.NOTION_CLIENT_ID!,
clientSecret: process.env.NOTION_CLIENT_SECRET!,
},
],
}),
],
});NOTION_CLIENT_ID=your-notion-oauth-client-id
NOTION_CLIENT_SECRET=secret_...Endpoints
| Endpoint | URL |
|---|---|
| Authorization | https://api.notion.com/v1/oauth/authorize |
| Token | https://api.notion.com/v1/oauth/token |
| User info | Embedded in token response (owner.user) |
Scopes
Notion does not use granular OAuth scopes. Permissions are configured at the integration level in the Notion UI. When a user authorizes your integration, they choose which pages and databases to share.
User data returned
| Field | Source | Notes |
|---|---|---|
id | owner.user.id | Stable Notion user UUID |
email | owner.user.person.email | Present only for person-type authorizations |
name | owner.user.name | Full display name |
avatar | owner.user.avatar_url | May be null if the user has no profile photo |
Notion returns user identity as part of the token exchange response — there is no separate /me endpoint. KavachOS caches the token payload internally so no extra network call is made.
The email field is only present when a person authorizes the integration. If a workspace bot is the owner (owner.type === "workspace"), the email will be absent. Always check for userInfo.email before using it.
Workspace data
The token response also includes workspace context, available in userInfo.raw:
const { userInfo } = await oauth.handleCallback('notion', code, state, redirectUri);
// Access workspace info from the raw token payload
const raw = userInfo.raw as { workspace_id: string; workspace_name: string };
console.log(raw.workspace_id); // e.g. "1234abcd-..."
console.log(raw.workspace_name); // e.g. "Acme Corp"This is useful for multi-workspace SaaS apps where you want to scope data per Notion workspace.