Skip to content

Commit e79fbed

Browse files
committed
feat(auth): add multi-credential support for authentication
- Introduced a new example demonstrating multi-credential authentication for Google and Microsoft providers. - Updated models to include `credentialId` for specifying credentials in authentication requests. - Enhanced the `Auth` resource to handle `credentialId` in OAuth URL generation. - Added tests to verify functionality of multi-credential authentication and URL generation with `credentialId`.
1 parent 7e55e6a commit e79fbed

6 files changed

Lines changed: 351 additions & 2 deletions

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/**
2+
* Multi-Credential Authentication Example
3+
*
4+
* This example demonstrates how to use the multi-credential feature to manage
5+
* multiple provider credentials under a single connector.
6+
*/
7+
8+
import Nylas from '../src/nylas';
9+
import { CredentialType } from '../src/models/credentials';
10+
11+
const nylas = new Nylas({
12+
apiKey: 'your-api-key',
13+
});
14+
15+
async function multiCredentialExample() {
16+
// Step 1: Create a connector (automatically creates a default credential)
17+
console.log('Step 1: Creating connector with default credentials...');
18+
const connector = await nylas.connectors.create({
19+
requestBody: {
20+
name: 'My Multi-Tenant Google Connector',
21+
provider: 'google',
22+
settings: {
23+
clientId: 'default-gcp-project-client-id',
24+
clientSecret: 'default-gcp-project-client-secret',
25+
topicName: 'default-topic',
26+
},
27+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
28+
},
29+
});
30+
console.log('Connector created:', connector.data.provider);
31+
console.log('Active credential ID:', connector.data.activeCredentialId);
32+
33+
// Step 2: Create additional credentials for different GCP projects
34+
console.log('\nStep 2: Creating additional credentials...');
35+
36+
const projectACredential = await nylas.connectors.credentials.create({
37+
provider: 'google',
38+
requestBody: {
39+
name: 'GCP Project A',
40+
credentialType: CredentialType.CONNECTOR,
41+
credentialData: {
42+
clientId: 'project-a-client-id',
43+
clientSecret: 'project-a-client-secret',
44+
topicName: 'project-a-topic',
45+
},
46+
},
47+
});
48+
console.log('Created credential for Project A:', projectACredential.data.id);
49+
50+
const projectBCredential = await nylas.connectors.credentials.create({
51+
provider: 'google',
52+
requestBody: {
53+
name: 'GCP Project B',
54+
credentialType: CredentialType.CONNECTOR,
55+
credentialData: {
56+
clientId: 'project-b-client-id',
57+
clientSecret: 'project-b-client-secret',
58+
topicName: 'project-b-topic',
59+
},
60+
},
61+
});
62+
console.log('Created credential for Project B:', projectBCredential.data.id);
63+
64+
// Step 3: List all credentials for this connector
65+
console.log('\nStep 3: Listing all credentials...');
66+
const credentialsList = await nylas.connectors.credentials.list({
67+
provider: 'google',
68+
});
69+
console.log(`Total credentials: ${credentialsList.data.length}`);
70+
71+
// Step 4: Use specific credential in Hosted OAuth flow
72+
console.log('\nStep 4: Generating hosted auth URL with Project A credentials...');
73+
const authUrlProjectA = nylas.auth.urlForOAuth2({
74+
clientId: 'your-nylas-app-client-id',
75+
redirectUri: 'https://your-app.com/callback',
76+
provider: 'google',
77+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
78+
credentialId: projectACredential.data.id, // Use Project A credentials
79+
});
80+
console.log('Auth URL for Project A:', authUrlProjectA);
81+
82+
// Step 5: Use different credential for another user
83+
console.log('\nStep 5: Generating hosted auth URL with Project B credentials...');
84+
const authUrlProjectB = nylas.auth.urlForOAuth2({
85+
clientId: 'your-nylas-app-client-id',
86+
redirectUri: 'https://your-app.com/callback',
87+
provider: 'google',
88+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
89+
credentialId: projectBCredential.data.id, // Use Project B credentials
90+
});
91+
console.log('Auth URL for Project B:', authUrlProjectB);
92+
93+
// Step 6: Use credential with PKCE flow
94+
console.log('\nStep 6: Generating PKCE auth URL with specific credential...');
95+
const pkceAuth = nylas.auth.urlForOAuth2PKCE({
96+
clientId: 'your-nylas-app-client-id',
97+
redirectUri: 'https://your-app.com/callback',
98+
provider: 'google',
99+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
100+
credentialId: projectACredential.data.id,
101+
});
102+
console.log('PKCE Auth URL:', pkceAuth.url);
103+
console.log('Secret (store this securely):', pkceAuth.secret);
104+
105+
// Step 7: Custom authentication with specific credential
106+
console.log('\nStep 7: Using custom authentication with specific credential...');
107+
const customGrant = await nylas.auth.customAuthentication({
108+
requestBody: {
109+
provider: 'google',
110+
settings: {
111+
// Provider-specific settings
112+
refresh_token: 'user-refresh-token',
113+
credentialId: projectBCredential.data.id, // Use Project B credentials
114+
},
115+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
116+
},
117+
});
118+
console.log('Grant created:', customGrant.data.id);
119+
console.log('Credential used:', customGrant.data.credentialId);
120+
121+
// Step 8: Update connector to change default credential
122+
console.log('\nStep 8: Updating connector default credential...');
123+
const updatedConnector = await nylas.connectors.update({
124+
provider: 'google',
125+
requestBody: {
126+
activeCredentialId: projectACredential.data.id, // Set Project A as default
127+
},
128+
});
129+
console.log('Updated active credential:', updatedConnector.data.activeCredentialId);
130+
131+
// Step 9: Use default credential (no credentialId specified)
132+
console.log('\nStep 9: Generating auth URL without specifying credential (uses default)...');
133+
const defaultAuthUrl = nylas.auth.urlForOAuth2({
134+
clientId: 'your-nylas-app-client-id',
135+
redirectUri: 'https://your-app.com/callback',
136+
provider: 'google',
137+
scope: ['https://www.googleapis.com/auth/gmail.readonly'],
138+
// No credentialId specified - will use activeCredentialId from connector
139+
});
140+
console.log('Auth URL using default credential:', defaultAuthUrl);
141+
142+
// Step 10: Cleanup - Delete a credential
143+
console.log('\nStep 10: Deleting Project B credential...');
144+
await nylas.connectors.credentials.destroy({
145+
provider: 'google',
146+
credentialsId: projectBCredential.data.id,
147+
});
148+
console.log('Credential deleted successfully');
149+
}
150+
151+
// Microsoft Multi-Tenant Example
152+
async function microsoftMultiTenantExample() {
153+
console.log('\n=== Microsoft Multi-Tenant Example ===\n');
154+
155+
// Create Microsoft connector
156+
const msConnector = await nylas.connectors.create({
157+
requestBody: {
158+
name: 'Multi-Tenant Microsoft Connector',
159+
provider: 'microsoft',
160+
settings: {
161+
clientId: 'default-tenant-client-id',
162+
clientSecret: 'default-tenant-client-secret',
163+
tenant: 'common',
164+
},
165+
scope: ['https://graph.microsoft.com/Mail.Read'],
166+
},
167+
});
168+
console.log('Microsoft connector created');
169+
170+
// Create credential for Tenant A
171+
const tenantACredential = await nylas.connectors.credentials.create({
172+
provider: 'microsoft',
173+
requestBody: {
174+
name: 'Tenant A',
175+
credentialType: CredentialType.CONNECTOR,
176+
credentialData: {
177+
clientId: 'tenant-a-client-id',
178+
clientSecret: 'tenant-a-client-secret',
179+
tenant: 'tenant-a-id',
180+
},
181+
},
182+
});
183+
console.log('Created credential for Tenant A:', tenantACredential.data.id);
184+
185+
// Create credential for Tenant B
186+
const tenantBCredential = await nylas.connectors.credentials.create({
187+
provider: 'microsoft',
188+
requestBody: {
189+
name: 'Tenant B',
190+
credentialType: CredentialType.CONNECTOR,
191+
credentialData: {
192+
clientId: 'tenant-b-client-id',
193+
clientSecret: 'tenant-b-client-secret',
194+
tenant: 'tenant-b-id',
195+
},
196+
},
197+
});
198+
console.log('Created credential for Tenant B:', tenantBCredential.data.id);
199+
200+
// Use Tenant A credentials for auth
201+
const tenantAAuthUrl = nylas.auth.urlForOAuth2({
202+
clientId: 'your-nylas-app-client-id',
203+
redirectUri: 'https://your-app.com/callback',
204+
provider: 'microsoft',
205+
scope: ['https://graph.microsoft.com/Mail.Read'],
206+
credentialId: tenantACredential.data.id,
207+
});
208+
console.log('Auth URL for Tenant A:', tenantAAuthUrl);
209+
210+
// Admin consent flow with specific credential
211+
const adminConsentUrl = nylas.auth.urlForAdminConsent({
212+
clientId: 'your-nylas-app-client-id',
213+
redirectUri: 'https://your-app.com/callback',
214+
credentialId: tenantBCredential.data.id,
215+
});
216+
console.log('Admin consent URL for Tenant B:', adminConsentUrl);
217+
}
218+
219+
// Run examples
220+
if (require.main === module) {
221+
multiCredentialExample()
222+
.then(() => microsoftMultiTenantExample())
223+
.then(() => console.log('\n✅ All examples completed successfully!'))
224+
.catch((error) => console.error('❌ Error:', error));
225+
}
226+
227+
export { multiCredentialExample, microsoftMultiTenantExample };

src/models/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ export interface URLForAuthenticationConfig {
6464
* send failures from missing SMTP configuration.
6565
*/
6666
smtpRequired?: boolean;
67+
/**
68+
* Optional credential ID to use for hosted authentication.
69+
* Allows selecting a specific set of provider credentials when multiple are configured under a connector.
70+
* If not specified, the connector's default credential will be used.
71+
* This is supported for standard OAuth flows (response_type=code).
72+
*/
73+
credentialId?: string;
6774
}
6875

6976
/**

src/models/connectors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export interface Connector {
1717
* Default scopes for the connector
1818
*/
1919
scope?: string[];
20+
/**
21+
* The active credential ID for the connector.
22+
* This is the default credential used for authentication when no specific credential is specified.
23+
*/
24+
activeCredentialId?: string;
2025
}
2126

2227
/**
@@ -132,6 +137,11 @@ export interface UpdateConnectorRequest {
132137
* The OAuth scopes
133138
*/
134139
scope?: string[];
140+
/**
141+
* The active credential ID to set as the default for this connector.
142+
* When specified, this credential will be used for authentication when no specific credential is provided.
143+
*/
144+
activeCredentialId?: string;
135145
}
136146

137147
/**

src/models/grants.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ export interface Grant {
5656
* Settings required by the provider that were sent as part of the OAuth request.
5757
*/
5858
settings?: Record<string, unknown>;
59+
/**
60+
* The credential ID that was used to create this grant.
61+
* This links the grant to a specific set of provider credentials.
62+
*/
63+
credentialId?: string;
5964
}
6065

6166
/**
@@ -68,8 +73,16 @@ export interface CreateGrantRequest {
6873
provider: Provider;
6974
/**
7075
* Settings required by provider.
71-
*/
72-
settings: Record<string, unknown>;
76+
* Can include 'credentialId' to specify which credential to use for authentication.
77+
* If not specified, the connector's default credential will be used.
78+
*/
79+
settings: Record<string, unknown> & {
80+
/**
81+
* Optional credential ID to use for this authentication.
82+
* Allows selecting a specific set of provider credentials when multiple are configured.
83+
*/
84+
credentialId?: string;
85+
};
7386
/**
7487
* Optional state value to return to developer's website after authentication flow is completed.
7588
*/

src/resources/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ export class Auth extends Resource {
218218
if (config.smtpRequired) {
219219
url.searchParams.set('options', 'smtp_required');
220220
}
221+
if (config.credentialId) {
222+
url.searchParams.set('credential_id', config.credentialId);
223+
}
221224

222225
return url;
223226
}

0 commit comments

Comments
 (0)