Nylas maintains the @nylas/connect library, 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?
Section titled “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?
Section titled “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
Standalone OAuth (Alternative)
Section titled “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)
Section titled “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
Section titled “How it works”- Frontend: Get a state parameter from your backend (linked to the authenticated user)
- Frontend: Open Nylas Connect popup with that state parameter
- Backend: Handle the OAuth callback, exchange code for tokens with
client_secret - Backend: Link the grant to the user via the state parameter
- Backend: Make all Nylas API calls using your API key
Frontend configuration
Section titled “Frontend configuration”import { useNylasConnect } from "@nylas/react";
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
Section titled “Backend state generation”// POST /api/oauth/init-stateimport crypto from "crypto";
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
Section titled “Backend callback handler”// GET /api/oauth/callbackimport { NylasConnect } from "@nylas/connect";
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
Section titled “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
Section titled “Understanding autoHandleCallback”When autoHandleCallback is true (the default), Nylas Connect automatically:
- Detects OAuth callback parameters (
code,state) in the URL - Exchanges the authorization code for tokens using PKCE (in the browser)
- Cleans up the URL by removing OAuth parameters
When your backend handles the callback:
- Set
autoHandleCallback: falseto prevent the browser from processing the callback - Point
redirectUrito your backend URL - Your backend exchanges the code using
client_secret(more secure than PKCE)
Multiple grants per user
Section titled “Multiple grants per user”To support multiple email accounts per user, use one of these strategies:
Option 1: Suffix strategy for external_user_id
// Each grant gets a unique external_user_idexternal_user_id: `${userId}:${Date.now()}`;external_user_id: `${userId}:google-1`;external_user_id: `${userId}:outlook-1`;Option 2: Store grant_id directly (simpler)
// Just store the grant_id in your databaseawait 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
Section titled “Making API calls from backend”Once grants are stored, make Nylas API calls using your API key:
// GET /api/emailsapp.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()
Section titled “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:
// ✅ CORRECT: Works for both popup and inline methodsconst callbackUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;const result = await nylasConnect.callback(callbackUrl);
// ❌ WRONG: handleRedirectCallback() only works for inline method in browserconst result = await nylasConnect.handleRedirectCallback();The url parameter is optional in the browser (it reads window.location) but required on the backend.
Before you begin
Section titled “Before you begin”For external IDP integration (Recommended)
Section titled “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
- The
@nylas/connectpackage installed
See the External Identity Provider guide for detailed setup instructions.
For standalone OAuth
Section titled “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/connectpackage installed in your environmentnpm install @nylas/connectpnpm add @nylas/connect
Configuration settings
Section titled “Configuration settings”With external identity provider (Recommended)
Section titled “With external identity provider (Recommended)”When using an external IDP, configure NylasConnect with the identityProviderToken callback:
import { NylasConnect } from "@nylas/connect";
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,});Standalone OAuth configuration
Section titled “Standalone OAuth configuration”The Nylas Connect library automatically detects the following environment variables:
# Client ID (required)NYLAS_CLIENT_ID=your_client_id# or for ViteVITE_NYLAS_CLIENT_ID=your_client_id
# Redirect URI (required)NYLAS_REDIRECT_URI=http://localhost:3000/auth/callback# or for ViteVITE_NYLAS_REDIRECT_URI=http://localhost:3000/auth/callbackYou can also configure manually:
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
Section titled “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.
const result = await nylasConnect.connect({ method: "popup", provider: "google", scopes: [ "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/calendar.readonly", ],});const result = await nylasConnect.connect({ method: "popup", provider: "microsoft",});const result = await nylasConnect.connect({ method: "popup", provider: "imap", // User will be prompted for IMAP settings});Set up basic authentication (Standalone OAuth)
Section titled “Set up basic authentication (Standalone OAuth)”The Connect library supports two ways to set up basic authentication: using a pop-up flow, or an inline flow.
Set up basic authentication using pop-up flow
Section titled “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).
import { NylasConnect } from "@nylas/connect";
// 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
Section titled “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.
// For redirect-based flowconst url = await nylasConnect.connect({ method: "inline" });window.location.href = url;// User will be redirected to OAuth providerHandle OAuth callback
Section titled “Handle OAuth callback”Now that you have basic authentication set up, you need to make sure your project can handle the OAuth callback.
// 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
Section titled “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.
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); }}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
Section titled “Manage authenticated grants”Nylas Connect makes calls to the Nylas APIs to manage grants’ connection status in your project.
Check grant connection status
Section titled “Check grant connection status”Your project should check users’ grant connection status intermittently to ensure they’re still authenticated.
// Check connection statusconst 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
Section titled “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.
// Logout current userawait nylasConnect.logout();
// Logout specific grantawait nylasConnect.logout("specific-grant-id");Set up single sign-on support
Section titled “Set up single sign-on support”You can add support for single sign-on (SSO) to your project by including the following code.
// Check for existing session on app loadconst 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
Section titled “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.
// Connect additional accountsconst secondAccount = await nylasConnect.connect({ method: "popup", provider: "microsoft",});
// Manage multiple grantsconst allSessions = await Promise.all([ nylasConnect.getSession("grant-1"), nylasConnect.getSession("grant-2"),]);
// Access grant information from each sessionallSessions.forEach((session) => { if (session?.grantInfo) { console.log(`Account: ${session.grantInfo.email}`); }});