kavachOS
Authentication

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/callback

Set Redirect URIs and save.

Copy credentials

Under Basic Information, copy the OAuth client ID and generate an OAuth client secret.

Configuration

lib/kavach.ts
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

EndpointURL
Authorizationhttps://api.notion.com/v1/oauth/authorize
Tokenhttps://api.notion.com/v1/oauth/token
User infoEmbedded 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

FieldSourceNotes
idowner.user.idStable Notion user UUID
emailowner.user.person.emailPresent only for person-type authorizations
nameowner.user.nameFull display name
avatarowner.user.avatar_urlMay 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.

On this page