# Nylas Connect library overview

Source: https://developer.nylas.com/docs/v3/auth/nylas-connect/

Nylas maintains the [`@nylas/connect` library](https://github.com/nylas/javascript/tree/main/packages/nylas-connect), a JavaScript/TypeScript library that you can use to add a simple authentication mechanism to your project. It automatically manages OAuth flows, token storage, and grant creation across multiple providers, meaning you can get to making API requests more quickly.

**The recommended way to use Nylas Connect is with external identity providers** (IDPs), allowing you to integrate email functionality with your existing authentication system.

## What are external identity providers?

External identity providers (IDPs) are authentication services that manage user identities and credentials for your application. Instead of building your own authentication system, you use a trusted provider to handle user sign-in, security, and identity management.

**Popular identity providers include:**

- **Auth0** - Enterprise authentication platform
- **Clerk** - Modern authentication with prebuilt UI components
- **Google Identity** - OAuth 2.0 authentication for Google accounts
- **WorkOS** - Enterprise SSO and directory sync
- **Custom IDPs** - Any identity provider with JWKS (JSON Web Key Set) endpoints

### Why use an external IDP with Nylas Connect?

Using an external IDP with Nylas Connect provides several benefits:

- **Unified authentication**: Users sign in once with your IDP, then connect their email accounts seamlessly
- **Simplified API calls**: Make Nylas API requests using your IDP tokens instead of managing separate API keys
- **Enhanced security**: Leverage your IDP's security features, compliance, and best practices
- **Better user experience**: Users don't need to authenticate separately for email functionality

> **Get started with external IDPs:** 
> Ready to integrate? Follow one of our provider-specific guides: [Auth0](/docs/v3/auth/nylas-connect/use-external-idp/auth0/), [Clerk](/docs/v3/auth/nylas-connect/use-external-idp/clerk/), [Google](/docs/v3/auth/nylas-connect/use-external-idp/google/), [WorkOS](/docs/v3/auth/nylas-connect/use-external-idp/workos/), or [custom IDP with JWKS](/docs/v3/auth/nylas-connect/use-external-idp/custom/).

## Standalone OAuth (Alternative)

If you don't have an existing authentication system or are building a simple prototype, you can use Nylas Connect for direct OAuth flows without an external IDP. The sections below show how to implement standalone OAuth authentication.

## Backend-Handled OAuth (Without IdP)

If you have your own authentication system (not using an external IdP like Clerk or Auth0) and want to:

- Use the popup UX for a seamless user experience
- Handle OAuth callbacks and token exchange on your **backend**
- Store Nylas tokens securely on the backend (not in the browser)
- Link multiple grants to a single user
- Make Nylas API calls from your backend using API keys

You can configure Nylas Connect to use the popup flow while your backend processes the OAuth callback.

### How it works

1. **Frontend**: Get a state parameter from your backend (linked to the authenticated user)
2. **Frontend**: Open Nylas Connect popup with that state parameter
3. **Backend**: Handle the OAuth callback, exchange code for tokens with `client_secret`
4. **Backend**: Link the grant to the user via the state parameter
5. **Backend**: Make all Nylas API calls using your API key

### Frontend configuration

```tsx


const { connect } = useNylasConnect({
  clientId: "your-client-id",
  // redirectUri points to your BACKEND callback URL
  redirectUri: "https://yourbackend.com/api/oauth/callback",
  // IMPORTANT: Disable auto-handling since backend processes the callback
  autoHandleCallback: false,
  // Don't store tokens in browser
  persistTokens: false,
});

async function handleConnect() {
  // 1. Get state from your backend (linked to authenticated user)
  const { state } = await fetch("/api/oauth/init-state", {
    credentials: "include",
  }).then((r) => r.json());

  // 2. Open popup with state - backend will handle the callback
  await connect({
    method: "popup",
    state,
    provider: "google", // optional
  });

  // 3. Popup closes automatically after backend processes callback
  console.log("Connected successfully!");
}
```

### Backend state generation

```typescript
// POST /api/oauth/init-state


app.post("/api/oauth/init-state", authenticatedMiddleware, async (req, res) => {
  const userId = req.user.id; // From your auth system (better-auth, Passport, etc.)
  const state = crypto.randomBytes(32).toString("hex");

  // Store state → userId mapping with 10-minute expiration
  await redis.setex(`oauth:state:${state}`, 600, userId);

  res.json({ state });
});
```

### Backend callback handler

```typescript
// GET /api/oauth/callback


const nylasConnect = new NylasConnect({
  clientId: process.env.NYLAS_CLIENT_ID,
  redirectUri: process.env.NYLAS_CALLBACK_URL,
});

app.get("/api/oauth/callback", async (req, res) => {
  try {
    const callbackUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;

    // Extract state and code from URL
    const urlParams = new URLSearchParams(new URL(callbackUrl).search);
    const state = urlParams.get("state");
    const code = urlParams.get("code");

    if (!state || !code) throw new Error("Missing OAuth parameters");

    // Retrieve user ID from state
    const userId = await redis.get(`oauth:state:${state}`);
    if (!userId) throw new Error("Invalid or expired state");

    // Exchange code for tokens using client_secret
    const tokenResponse = await fetch(
      "https://api.us.nylas.com/connect/token",
      {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Authorization: `Bearer ${process.env.NYLAS_API_KEY}`,
        },
        body: new URLSearchParams({
          client_id: process.env.NYLAS_CLIENT_ID,
          client_secret: process.env.NYLAS_CLIENT_SECRET,
          redirect_uri: process.env.NYLAS_CALLBACK_URL,
          code,
          grant_type: "authorization_code",
          // Optional: For multiple grants per user, use a suffix strategy
          external_user_id: `${userId}:${Date.now()}`,
        }),
      },
    );

    const tokenData = await tokenResponse.json();

    // Store grant in your database
    await db.grants.create({
      userId,
      grantId: tokenData.grant_id,
      email: tokenData.email,
      provider: tokenData.provider,
      accessToken: tokenData.access_token,
      refreshToken: tokenData.refresh_token,
    });

    // Clean up state
    await redis.del(`oauth:state:${state}`);

    // Close popup window
    res.send(`
      <html><body>
        <script>
          window.opener?.postMessage({ type: 'NYLAS_CONNECT_SUCCESS' }, '*');
          window.close();
        </script>
        <p>Authentication successful! Closing window...</p>
      </body></html>
    `);
  } catch (error) {
    res.send(`
      <html><body>
        <script>
          window.opener?.postMessage({ 
            type: 'NYLAS_CONNECT_ERROR',
            error: '${error.message}'
          }, '*');
          window.close();
        </script>
        <p>Error: ${error.message}</p>
      </body></html>
    `);
  }
});
```

### Key configuration options

| Setting              | Value                                                        | Why                                                                |
| -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ |
| `redirectUri`        | Backend URL (e.g., `https://api.example.com/oauth/callback`) | Backend processes the callback, not frontend                       |
| `autoHandleCallback` | `false`                                                      | Prevents browser from trying to exchange code (backend handles it) |
| `persistTokens`      | `false`                                                      | Keeps tokens out of browser storage                                |
| `state` parameter    | From backend                                                 | Securely links OAuth flow to authenticated user                    |

### Understanding `autoHandleCallback`

When `autoHandleCallback` is `true` (the default), Nylas Connect automatically:

1. Detects OAuth callback parameters (`code`, `state`) in the URL
2. Exchanges the authorization code for tokens using PKCE (in the browser)
3. Cleans up the URL by removing OAuth parameters

When your **backend** handles the callback:

- Set `autoHandleCallback: false` to prevent the browser from processing the callback
- Point `redirectUri` to your backend URL
- Your backend exchanges the code using `client_secret` (more secure than PKCE)

### Multiple grants per user

To support multiple email accounts per user, use one of these strategies:

**Option 1: Suffix strategy for `external_user_id`**

```typescript
// Each grant gets a unique external_user_id
external_user_id: `${userId}:${Date.now()}`;
external_user_id: `${userId}:google-1`;
external_user_id: `${userId}:outlook-1`;
```

**Option 2: Store grant_id directly (simpler)**

```typescript
// Just store the grant_id in your database
await db.grants.create({
  userId: userId, // Your user ID
  grantId: tokenData.grant_id, // Nylas grant ID
  email: tokenData.email,
  provider: tokenData.provider,
});
```

Most developers find Option 2 simpler since you can query by `userId` directly without parsing `external_user_id`.

### Making API calls from backend

Once grants are stored, make Nylas API calls using your API key:

```typescript
// GET /api/emails
app.get("/api/emails", authenticatedMiddleware, async (req, res) => {
  const userId = req.user.id;

  // Get user's grants from database
  const grants = await db.grants.findMany({ where: { userId } });

  // Make API calls using Nylas API key (not user tokens)
  const emails = await Promise.all(
    grants.map(async (grant) => {
      const response = await fetch(
        `https://api.us.nylas.com/v3/grants/${grant.grantId}/messages`,
        {
          headers: {
            Authorization: `Bearer ${process.env.NYLAS_API_KEY}`,
          },
        },
      );
      return response.json();
    }),
  );

  res.json(emails);
});
```

### `callback()` vs `handleRedirectCallback()`

Nylas Connect provides two callback methods:

| Method                     | Use Case          | Supports Popup? | Supports Inline? | Where to Use   |
| -------------------------- | ----------------- | --------------- | ---------------- | -------------- |
| `callback(url?)`           | Backend handling  | ✅ Yes          | ✅ Yes           | Backend routes |
| `handleRedirectCallback()` | Frontend handling | ❌ No           | ✅ Yes           | Browser only   |

**Important:** When handling callbacks on the backend, always use `callback(url)` with the full callback URL:

```typescript
// ✅ CORRECT: Works for both popup and inline methods
const callbackUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const result = await nylasConnect.callback(callbackUrl);

// ❌ WRONG: handleRedirectCallback() only works for inline method in browser
const result = await nylasConnect.handleRedirectCallback();
```

The `url` parameter is optional in the browser (it reads `window.location`) but required on the backend.

## Before you begin

### For external IDP integration (Recommended)

If you're using an external identity provider, you need:

- Your identity provider account (Auth0, Clerk, Google, WorkOS, etc.)
- Your Nylas application's client ID
- Allowed origins and callback URIs configured in the [Nylas Dashboard IDP settings](https://dashboard-v3.nylas.com)
- The [`@nylas/connect` package](https://github.com/nylas/javascript/tree/main/packages/nylas-connect) installed

See the [External Identity Provider guide](/docs/v3/auth/nylas-connect/use-external-idp/) for detailed setup instructions.

### For standalone OAuth

If you're using standalone OAuth (without an external IDP), you need:

- A working OAuth redirect URI
- Your Nylas application's client ID
- The [`@nylas/connect` package](https://github.com/nylas/javascript/tree/main/packages/nylas-connect) installed in your environment

  ```bash [install-npm]
  npm install @nylas/connect
  ```

  ```bash [install-pnpm]
  pnpm add @nylas/connect
  ```

### Configuration settings

#### With external identity provider (Recommended)

When using an external IDP, configure `NylasConnect` with the `identityProviderToken` callback:

```ts


const nylasConnect = new NylasConnect({
  clientId: "<NYLAS_CLIENT_ID>",
  redirectUri: "http://localhost:3000/auth/callback",
  apiUrl: "https://api.us.nylas.com", // or https://api.eu.nylas.com
  // Provide your IDP's access token
  identityProviderToken: async () => {
    // Return your IDP token (from Auth0, Clerk, Google, etc.)
    return await getYourIdpToken();
  },
  environment: "production",
  persistTokens: true,
});
```

> **Using Custom IDPs with JWKS:** 
> Nylas Connect works with any identity provider that exposes a JWKS (JSON Web Key Set) endpoint. This includes custom authentication systems, enterprise IDPs, and open-source solutions like Keycloak or Ory. Simply configure your IDP's JWKS endpoint in the Nylas Dashboard and provide tokens via the `identityProviderToken` callback.

#### Standalone OAuth configuration

The Nylas Connect library automatically detects the following environment variables:

```bash
# Client ID (required)
NYLAS_CLIENT_ID=your_client_id
# or for Vite
VITE_NYLAS_CLIENT_ID=your_client_id

# Redirect URI (required)
NYLAS_REDIRECT_URI=http://localhost:3000/auth/callback
# or for Vite
VITE_NYLAS_REDIRECT_URI=http://localhost:3000/auth/callback
```

You can also configure manually:

```ts
const nylasConnect = new NylasConnect({
  clientId: "<NYLAS_CLIENT_ID>",
  redirectUri: "http://localhost:3000/auth/callback",
  apiUrl: "https://api.us.nylas.com", // or https://api.eu.nylas.com
  defaultScopes: [], // If not provided, we default to your connector scopes
  environment: "development", // "development" | "staging" | "production"
  persistTokens: true, // Store tokens in localStorage
  debug: true, // Enable debug logging
  logLevel: "info", // "error" | "warn" | "info" | "debug" | "off"
});
```

### Provider-specific configuration settings

Every provider requests different information as part of its OAuth flow. You'll need to configure the information you pass for each provider your project supports.

```ts
const result = await nylasConnect.connect({
  method: "popup",
  provider: "google",
  scopes: [
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/calendar.readonly",
  ],
  loginHint: "user@example.com", // Pre-fill email address
});
```

```ts
const result = await nylasConnect.connect({
  method: "popup",
  provider: "microsoft",
  loginHint: "user@company.com",
});
```

```ts
const result = await nylasConnect.connect({
  method: "popup",
  provider: "imap",
  // User will be prompted for IMAP settings
});
```

## Set up basic authentication (Standalone OAuth)

> **Caution:** 
> The following sections describe standalone OAuth implementation **without** an external identity provider. For most applications, we recommend [using an external IDP](/docs/v3/auth/nylas-connect/use-external-idp/) instead.

The Connect library supports two ways to set up basic authentication: using a [pop-up flow](#set-up-basic-authentication-using-pop-up-flow), or an [inline flow](#set-up-basic-authentication-using-inline-flow).

### Set up basic authentication using pop-up flow

When you use the pop-up flow, your project opens the OAuth prompt in a pop-up window and the user authenticates without leaving your application. This method works well if you're creating a single-page application (SPA).

```ts


// Initialize with environment variables (recommended)
const nylasConnect = new NylasConnect();

try {
  // Start OAuth flow in popup window
  const result = await nylasConnect.connect({ method: "popup" });

  // Extract grant information
  const { grantId, grantInfo } = result;
  console.log(`Connected ${grantInfo?.email} via ${grantInfo?.provider}`);

  // Store grantId for future API calls
  localStorage.setItem("nylasGrantId", grantId);
} catch (error) {
  console.error("Authentication failed:", error);
}
```

### Set up basic authentication using inline flow

When you use the inline flow, your project redirects the user's browser to the OAuth provider where they authenticate. After the authentication process is complete, the provider returns the user to your project. We recommend this method for mobile browsers or other scenarios where the user's browser might not support pop-ups.

```ts
// For redirect-based flow
const url = await nylasConnect.connect({ method: "inline" });
window.location.href = url;
// User will be redirected to OAuth provider
```

## Handle OAuth callback

Now that you have basic authentication set up, you need to make sure your project can handle the OAuth callback.

```ts
// On your callback page (e.g., /auth/callback)
try {
  const result = await nylasConnect.callback();
  const { grantId, email } = result;
  console.log(`Successfully authenticated: ${email}`);
} catch (error) {
  console.error("Callback handling failed:", error);
}
```

## Set up error handling

You can set up error handling for your authentication flow using either the try-catch method or state change events that Nylas Connect generates.

```ts
try {
  const result = await nylasConnect.connect({ method: "popup" });
  console.log("Connection successful:", result);
} catch (error) {
  if (error.name === "PopupError") {
    console.error("Popup was blocked or closed");
  } else if (error.name === "ConfigError") {
    console.error("Configuration error:", error.message);
  } else if (error.name === "OAuthError") {
    console.error("OAuth error:", error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}
```

```ts [error-State change events]
nylasConnect.onConnectStateChange((event, session, data) => {
  switch (event) {
    case "CONNECT_SUCCESS":
      console.log("Successfully connected:", session?.grantInfo?.email);
      break;
    case "CONNECT_ERROR":
      console.error("Connection failed:", data?.error);
      break;
    case "CONNECT_CANCELLED":
      console.log("User cancelled authentication");
      break;
    case "CONNECT_STARTED":
      console.log("Authentication started");
      break;
  }
});
```

## Manage authenticated grants

Nylas Connect makes calls to the Nylas APIs to manage grants' connection status in your project.

### Check grant connection status

Your project should check users' grant connection status intermittently to ensure they're still authenticated.

```ts
// Check connection status
const status = await nylasConnect.getConnectionStatus();
console.log(`Connection status: ${status}`); // "connected" | "expired" | "invalid" | "not_connected"

// Get session information (includes grant info)
const session = await nylasConnect.getSession();
if (session?.grantInfo) {
  console.log(`Connected as: ${session.grantInfo.email}`);
  console.log(`Provider: ${session.grantInfo.provider}`);
  console.log(`Grant ID: ${session.grantId}`);
}
```

### Log a user out

When a user chooses to log out of your project, you need to call `nylasConnect.logout()`. If you don't pass a grant ID in your call, Nylas Connect logs the current user out.

```ts
// Logout current user
await nylasConnect.logout();

// Logout specific grant
await nylasConnect.logout("specific-grant-id");
```

## Set up single sign-on support

You can add support for single sign-on (SSO) to your project by including the following code.

```ts
// Check for existing session on app load
const session = await nylasConnect.getSession();

if (session) {
  // User is already authenticated, proceed to app
  initializeApp(session.grantInfo);
} else {
  // Show login button
  showLoginButton();
}
```

## Set up support for multiple accounts

If you want to allow your users to authenticate multiple accounts to your project, you can configure Nylas Connect to get information about all of their authenticated grants.

```ts
// Connect additional accounts
const secondAccount = await nylasConnect.connect({
  method: "popup",
  provider: "microsoft",
});

// Manage multiple grants
const allSessions = await Promise.all([
  nylasConnect.getSession("grant-1"),
  nylasConnect.getSession("grant-2"),
]);

// Access grant information from each session
allSessions.forEach((session) => {
  if (session?.grantInfo) {
    console.log(`Account: ${session.grantInfo.email}`);
  }
});
```