chub-dev 0.1.0 → 0.1.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1813 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auth
|
|
3
|
+
description: "Stytch Node.js SDK authentication guide for passwordless and OTP-based authentication"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "12.43.0"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "stytch,auth,authentication,passwordless,otp"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Stytch Node.js SDK - Authentication Guide
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**ALWAYS use the official `stytch` package from npm.**
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install stytch
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Current version: 12.43.0**
|
|
23
|
+
|
|
24
|
+
**DO NOT use:**
|
|
25
|
+
- Deprecated `@stytch/stytch-js` (deprecated in favor of `@stytch/vanilla-js` for frontend)
|
|
26
|
+
- Any unofficial or outdated Stytch packages
|
|
27
|
+
- Frontend packages (`@stytch/vanilla-js`, `@stytch/react`, `@stytch/nextjs`) when building backend services
|
|
28
|
+
|
|
29
|
+
The `stytch` package is the official backend SDK for Node.js applications. It supports TypeScript and all current LTS versions of Node.js (18+).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Install the SDK
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install stytch
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
or
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
yarn add stytch
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Environment Variables
|
|
48
|
+
|
|
49
|
+
Set up your Stytch credentials in your environment:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
STYTCH_PROJECT_ID=project-live-your-project-id
|
|
53
|
+
STYTCH_SECRET=secret-live-your-secret-key
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Get credentials from:** [Stytch Dashboard](https://stytch.com/dashboard)
|
|
57
|
+
|
|
58
|
+
For testing, use test environment credentials:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
STYTCH_PROJECT_ID=project-test-your-project-id
|
|
62
|
+
STYTCH_SECRET=secret-test-your-secret-key
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### .env File Example
|
|
66
|
+
|
|
67
|
+
```env
|
|
68
|
+
STYTCH_PROJECT_ID=project-live-c60c0abe-c25a-4472-a9ed-320c6667d317
|
|
69
|
+
STYTCH_SECRET=secret-live-80JASucyk7z_G8Z-7dVwZVGXL5NT_qGAQ2I=
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Initialization
|
|
75
|
+
|
|
76
|
+
### Basic B2C Client
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
const stytch = require('stytch');
|
|
80
|
+
|
|
81
|
+
const client = new stytch.Client({
|
|
82
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
83
|
+
secret: process.env.STYTCH_SECRET,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### ES6 Module Import
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import * as stytch from 'stytch';
|
|
91
|
+
|
|
92
|
+
const client = new stytch.Client({
|
|
93
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
94
|
+
secret: process.env.STYTCH_SECRET,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### B2B Client (for organizations)
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const stytch = require('stytch');
|
|
102
|
+
|
|
103
|
+
const b2bClient = new stytch.B2BClient({
|
|
104
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
105
|
+
secret: process.env.STYTCH_SECRET,
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Client with Environment Override
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const client = new stytch.Client({
|
|
113
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
114
|
+
secret: process.env.STYTCH_SECRET,
|
|
115
|
+
environment: 'test', // or 'live' (default)
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Full Configuration Options
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
const client = new stytch.Client({
|
|
123
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
124
|
+
secret: process.env.STYTCH_SECRET,
|
|
125
|
+
environment: 'live',
|
|
126
|
+
timeout: 30000, // Request timeout in milliseconds
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Core API Surfaces
|
|
133
|
+
|
|
134
|
+
### 1. Magic Links
|
|
135
|
+
|
|
136
|
+
Magic links provide passwordless authentication via email.
|
|
137
|
+
|
|
138
|
+
#### Send Magic Link (Login or Create)
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Minimal example
|
|
142
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
143
|
+
email: 'user@example.com',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
console.log(response.user_id);
|
|
147
|
+
console.log(response.email_id);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Send Magic Link with Custom URLs
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
154
|
+
email: 'user@example.com',
|
|
155
|
+
login_magic_link_url: 'https://example.com/authenticate?token={{token}}',
|
|
156
|
+
signup_magic_link_url: 'https://example.com/authenticate?token={{token}}',
|
|
157
|
+
login_expiration_minutes: 15,
|
|
158
|
+
signup_expiration_minutes: 60,
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### Advanced Magic Link with Options
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
166
|
+
email: 'user@example.com',
|
|
167
|
+
login_magic_link_url: 'https://example.com/authenticate',
|
|
168
|
+
signup_magic_link_url: 'https://example.com/authenticate',
|
|
169
|
+
login_expiration_minutes: 15,
|
|
170
|
+
signup_expiration_minutes: 60,
|
|
171
|
+
login_template_id: 'custom-login-template',
|
|
172
|
+
signup_template_id: 'custom-signup-template',
|
|
173
|
+
attributes: {
|
|
174
|
+
ip_address: '192.168.1.1',
|
|
175
|
+
},
|
|
176
|
+
code_challenge: 'challenge_string', // For PKCE flow
|
|
177
|
+
user_id: 'user-123', // Associate with existing user
|
|
178
|
+
session_duration_minutes: 60,
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Authenticate Magic Link
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// Minimal example
|
|
186
|
+
const response = await client.magicLinks.authenticate({
|
|
187
|
+
token: 'DOYoip3rvIMMW5lgItikFK-Ak1CfMsgjuiCyI7uuU94=',
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
console.log(response.user);
|
|
191
|
+
console.log(response.session_token);
|
|
192
|
+
console.log(response.session_jwt);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Authenticate with Session Options
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
const response = await client.magicLinks.authenticate({
|
|
199
|
+
token: 'DOYoip3rvIMMW5lgItikFK-Ak1CfMsgjuiCyI7uuU94=',
|
|
200
|
+
session_duration_minutes: 60,
|
|
201
|
+
session_custom_claims: {
|
|
202
|
+
custom_claim: 'value',
|
|
203
|
+
},
|
|
204
|
+
attributes: {
|
|
205
|
+
ip_address: '192.168.1.1',
|
|
206
|
+
user_agent: 'Mozilla/5.0...',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
#### Send Magic Link Only (No Auto-Create)
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
const response = await client.magicLinks.email.send({
|
|
215
|
+
email: 'user@example.com',
|
|
216
|
+
login_magic_link_url: 'https://example.com/authenticate',
|
|
217
|
+
login_expiration_minutes: 15,
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Create Embeddable Magic Link
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const response = await client.magicLinks.email.createEmbeddable({
|
|
225
|
+
user_id: 'user-live-123',
|
|
226
|
+
embeddable_magic_link_url: 'https://example.com/authenticate',
|
|
227
|
+
expiration_minutes: 10,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
console.log(response.token); // Use in your own emails
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### 2. One-Time Passcodes (OTP)
|
|
236
|
+
|
|
237
|
+
#### SMS OTP
|
|
238
|
+
|
|
239
|
+
**Send OTP via SMS:**
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// Minimal example
|
|
243
|
+
const response = await client.otps.sms.send({
|
|
244
|
+
phone_number: '+15555555555',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
console.log(response.phone_id);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Send OTP with Options:**
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
const response = await client.otps.sms.send({
|
|
254
|
+
phone_number: '+15555555555',
|
|
255
|
+
expiration_minutes: 10,
|
|
256
|
+
attributes: {
|
|
257
|
+
ip_address: '192.168.1.1',
|
|
258
|
+
},
|
|
259
|
+
user_id: 'user-123', // Associate with existing user
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Authenticate SMS OTP:**
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
// Minimal example
|
|
267
|
+
const response = await client.otps.authenticate({
|
|
268
|
+
method_id: 'phone-id-123',
|
|
269
|
+
code: '123456',
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Authenticate with Session:**
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
const response = await client.otps.authenticate({
|
|
277
|
+
method_id: 'phone-id-123',
|
|
278
|
+
code: '123456',
|
|
279
|
+
session_duration_minutes: 60,
|
|
280
|
+
attributes: {
|
|
281
|
+
ip_address: '192.168.1.1',
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
console.log(response.user);
|
|
286
|
+
console.log(response.session_token);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### Email OTP
|
|
290
|
+
|
|
291
|
+
**Send OTP via Email:**
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
// Minimal example
|
|
295
|
+
const response = await client.otps.email.send({
|
|
296
|
+
email: 'user@example.com',
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Send with Options:**
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
const response = await client.otps.email.send({
|
|
304
|
+
email: 'user@example.com',
|
|
305
|
+
expiration_minutes: 10,
|
|
306
|
+
login_template_id: 'custom-template',
|
|
307
|
+
user_id: 'user-123',
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Authenticate Email OTP:**
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const response = await client.otps.authenticate({
|
|
315
|
+
method_id: 'email-id-123',
|
|
316
|
+
code: '123456',
|
|
317
|
+
session_duration_minutes: 60,
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### WhatsApp OTP
|
|
322
|
+
|
|
323
|
+
**Send OTP via WhatsApp:**
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
const response = await client.otps.whatsapp.send({
|
|
327
|
+
phone_number: '+15555555555',
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Authenticate WhatsApp OTP:**
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const response = await client.otps.authenticate({
|
|
335
|
+
method_id: 'phone-id-123',
|
|
336
|
+
code: '123456',
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### Login or Create User with SMS
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
const response = await client.otps.sms.loginOrCreate({
|
|
344
|
+
phone_number: '+15555555555',
|
|
345
|
+
expiration_minutes: 10,
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### Login or Create User with Email
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const response = await client.otps.email.loginOrCreate({
|
|
353
|
+
email: 'user@example.com',
|
|
354
|
+
expiration_minutes: 10,
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### 3. OAuth
|
|
361
|
+
|
|
362
|
+
OAuth enables authentication via third-party providers.
|
|
363
|
+
|
|
364
|
+
#### Start OAuth Flow
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
// Get OAuth authorization URL
|
|
368
|
+
const response = await client.oauth.start({
|
|
369
|
+
provider: 'google',
|
|
370
|
+
signup_redirect_url: 'https://example.com/authenticate',
|
|
371
|
+
login_redirect_url: 'https://example.com/authenticate',
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
console.log(response.oauth_url); // Redirect user here
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
#### Start OAuth with Options
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
const response = await client.oauth.start({
|
|
381
|
+
provider: 'google',
|
|
382
|
+
signup_redirect_url: 'https://example.com/authenticate',
|
|
383
|
+
login_redirect_url: 'https://example.com/authenticate',
|
|
384
|
+
custom_scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
|
|
385
|
+
provider_params: {
|
|
386
|
+
access_type: 'offline',
|
|
387
|
+
},
|
|
388
|
+
code_challenge: 'challenge_string', // For PKCE
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Authenticate OAuth
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
// Minimal example
|
|
396
|
+
const response = await client.oauth.authenticate({
|
|
397
|
+
token: 'oauth-token-from-callback',
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
console.log(response.user);
|
|
401
|
+
console.log(response.session_token);
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### Authenticate with Session Options
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const response = await client.oauth.authenticate({
|
|
408
|
+
token: 'oauth-token-from-callback',
|
|
409
|
+
session_duration_minutes: 60,
|
|
410
|
+
session_custom_claims: {
|
|
411
|
+
custom_claim: 'value',
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### Supported OAuth Providers
|
|
417
|
+
|
|
418
|
+
- `google`
|
|
419
|
+
- `microsoft`
|
|
420
|
+
- `facebook`
|
|
421
|
+
- `github`
|
|
422
|
+
- `gitlab`
|
|
423
|
+
- `slack`
|
|
424
|
+
- `linkedin`
|
|
425
|
+
- `amazon`
|
|
426
|
+
- `bitbucket`
|
|
427
|
+
- `coinbase`
|
|
428
|
+
- `discord`
|
|
429
|
+
- `figma`
|
|
430
|
+
- `hubspot`
|
|
431
|
+
- `salesforce`
|
|
432
|
+
- `shopify`
|
|
433
|
+
- `snapchat`
|
|
434
|
+
- `tiktok`
|
|
435
|
+
- `twitch`
|
|
436
|
+
- `twitter`
|
|
437
|
+
- `yahoo`
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### 4. Sessions
|
|
442
|
+
|
|
443
|
+
Sessions manage authenticated user state.
|
|
444
|
+
|
|
445
|
+
#### Authenticate Session
|
|
446
|
+
|
|
447
|
+
```javascript
|
|
448
|
+
// Minimal example
|
|
449
|
+
const response = await client.sessions.authenticate({
|
|
450
|
+
session_token: 'session-token-here',
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
console.log(response.user);
|
|
454
|
+
console.log(response.session); // Contains session data
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### Authenticate with JWT
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
const response = await client.sessions.authenticateJwt({
|
|
461
|
+
session_jwt: 'jwt-token-here',
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Authenticate Session with Refresh
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
const response = await client.sessions.authenticate({
|
|
469
|
+
session_token: 'session-token-here',
|
|
470
|
+
session_duration_minutes: 60, // Extend session
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// Store new token
|
|
474
|
+
const newSessionToken = response.session_token;
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### Get Session
|
|
478
|
+
|
|
479
|
+
```javascript
|
|
480
|
+
const response = await client.sessions.get({
|
|
481
|
+
user_id: 'user-123',
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
console.log(response.sessions); // All active sessions
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### Revoke Session
|
|
488
|
+
|
|
489
|
+
```javascript
|
|
490
|
+
await client.sessions.revoke({
|
|
491
|
+
session_token: 'session-token-to-revoke',
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### Revoke Session by ID
|
|
496
|
+
|
|
497
|
+
```javascript
|
|
498
|
+
await client.sessions.revoke({
|
|
499
|
+
session_id: 'session-id-123',
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
#### Migrate Session
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
const response = await client.sessions.migrate({
|
|
507
|
+
session_token: 'old-session-token',
|
|
508
|
+
session_duration_minutes: 60,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
console.log(response.session_token); // New token
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
### 5. Users
|
|
517
|
+
|
|
518
|
+
Manage user accounts and data.
|
|
519
|
+
|
|
520
|
+
#### Get User
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
const response = await client.users.get({
|
|
524
|
+
user_id: 'user-123',
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
console.log(response.user);
|
|
528
|
+
console.log(response.user.emails);
|
|
529
|
+
console.log(response.user.phone_numbers);
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Create User
|
|
533
|
+
|
|
534
|
+
```javascript
|
|
535
|
+
const response = await client.users.create({
|
|
536
|
+
email: 'user@example.com',
|
|
537
|
+
name: {
|
|
538
|
+
first_name: 'John',
|
|
539
|
+
last_name: 'Doe',
|
|
540
|
+
},
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Create User with Multiple Methods
|
|
545
|
+
|
|
546
|
+
```javascript
|
|
547
|
+
const response = await client.users.create({
|
|
548
|
+
email: 'user@example.com',
|
|
549
|
+
phone_number: '+15555555555',
|
|
550
|
+
name: {
|
|
551
|
+
first_name: 'John',
|
|
552
|
+
last_name: 'Doe',
|
|
553
|
+
},
|
|
554
|
+
attributes: {
|
|
555
|
+
custom_attribute: 'value',
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Update User
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
const response = await client.users.update({
|
|
564
|
+
user_id: 'user-123',
|
|
565
|
+
name: {
|
|
566
|
+
first_name: 'Jane',
|
|
567
|
+
last_name: 'Smith',
|
|
568
|
+
},
|
|
569
|
+
attributes: {
|
|
570
|
+
custom_field: 'new_value',
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
#### Delete User
|
|
576
|
+
|
|
577
|
+
```javascript
|
|
578
|
+
await client.users.delete({
|
|
579
|
+
user_id: 'user-123',
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
#### Search Users
|
|
584
|
+
|
|
585
|
+
```javascript
|
|
586
|
+
// Basic search
|
|
587
|
+
const response = await client.users.search({
|
|
588
|
+
query: {
|
|
589
|
+
operator: 'AND',
|
|
590
|
+
operands: [
|
|
591
|
+
{
|
|
592
|
+
filter_name: 'status',
|
|
593
|
+
filter_value: ['active'],
|
|
594
|
+
},
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
console.log(response.results);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
#### Advanced User Search
|
|
603
|
+
|
|
604
|
+
```javascript
|
|
605
|
+
const response = await client.users.search({
|
|
606
|
+
query: {
|
|
607
|
+
operator: 'AND',
|
|
608
|
+
operands: [
|
|
609
|
+
{
|
|
610
|
+
filter_name: 'email_verified',
|
|
611
|
+
filter_value: [true],
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
filter_name: 'created_at',
|
|
615
|
+
filter_value: {
|
|
616
|
+
greater_than: '2024-01-01T00:00:00Z',
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
],
|
|
620
|
+
},
|
|
621
|
+
limit: 100,
|
|
622
|
+
cursor: 'cursor-from-previous-request',
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### Delete Email
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
await client.users.deleteEmail({
|
|
630
|
+
email_id: 'email-id-123',
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
#### Delete Phone Number
|
|
635
|
+
|
|
636
|
+
```javascript
|
|
637
|
+
await client.users.deletePhoneNumber({
|
|
638
|
+
phone_id: 'phone-id-123',
|
|
639
|
+
});
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### 6. Passwords
|
|
645
|
+
|
|
646
|
+
Traditional password authentication.
|
|
647
|
+
|
|
648
|
+
#### Create Password
|
|
649
|
+
|
|
650
|
+
```javascript
|
|
651
|
+
const response = await client.passwords.create({
|
|
652
|
+
email: 'user@example.com',
|
|
653
|
+
password: 'SecurePassword123!',
|
|
654
|
+
session_duration_minutes: 60,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
console.log(response.user);
|
|
658
|
+
console.log(response.session_token);
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
#### Authenticate Password
|
|
662
|
+
|
|
663
|
+
```javascript
|
|
664
|
+
// Minimal example
|
|
665
|
+
const response = await client.passwords.authenticate({
|
|
666
|
+
email: 'user@example.com',
|
|
667
|
+
password: 'SecurePassword123!',
|
|
668
|
+
});
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### Authenticate with Session
|
|
672
|
+
|
|
673
|
+
```javascript
|
|
674
|
+
const response = await client.passwords.authenticate({
|
|
675
|
+
email: 'user@example.com',
|
|
676
|
+
password: 'SecurePassword123!',
|
|
677
|
+
session_duration_minutes: 60,
|
|
678
|
+
session_custom_claims: {
|
|
679
|
+
custom_claim: 'value',
|
|
680
|
+
},
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
console.log(response.session_token);
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
#### Strength Check
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
const response = await client.passwords.strengthCheck({
|
|
690
|
+
email: 'user@example.com',
|
|
691
|
+
password: 'password-to-check',
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
console.log(response.valid_password);
|
|
695
|
+
console.log(response.score);
|
|
696
|
+
console.log(response.breached_password);
|
|
697
|
+
console.log(response.strength_policy);
|
|
698
|
+
console.log(response.breach_detection_on_create);
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
#### Initiate Password Reset
|
|
702
|
+
|
|
703
|
+
```javascript
|
|
704
|
+
const response = await client.passwords.email.resetStart({
|
|
705
|
+
email: 'user@example.com',
|
|
706
|
+
reset_password_redirect_url: 'https://example.com/reset',
|
|
707
|
+
reset_password_expiration_minutes: 30,
|
|
708
|
+
});
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Reset Password by Email
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
const response = await client.passwords.email.reset({
|
|
715
|
+
token: 'reset-token-from-email',
|
|
716
|
+
password: 'NewSecurePassword123!',
|
|
717
|
+
session_duration_minutes: 60,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
console.log(response.user);
|
|
721
|
+
console.log(response.session_token);
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
#### Reset Password by Session
|
|
725
|
+
|
|
726
|
+
```javascript
|
|
727
|
+
const response = await client.passwords.session.reset({
|
|
728
|
+
session_token: 'active-session-token',
|
|
729
|
+
password: 'NewSecurePassword123!',
|
|
730
|
+
});
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
#### Migrate Password (from existing system)
|
|
734
|
+
|
|
735
|
+
```javascript
|
|
736
|
+
const response = await client.passwords.migrate({
|
|
737
|
+
email: 'user@example.com',
|
|
738
|
+
hash: '$2a$10$...', // bcrypt hash
|
|
739
|
+
hash_type: 'bcrypt',
|
|
740
|
+
name: {
|
|
741
|
+
first_name: 'John',
|
|
742
|
+
last_name: 'Doe',
|
|
743
|
+
},
|
|
744
|
+
});
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
### 7. WebAuthn
|
|
750
|
+
|
|
751
|
+
Passwordless authentication using biometrics or security keys.
|
|
752
|
+
|
|
753
|
+
#### Register WebAuthn Start
|
|
754
|
+
|
|
755
|
+
```javascript
|
|
756
|
+
const response = await client.webauthn.registerStart({
|
|
757
|
+
user_id: 'user-123',
|
|
758
|
+
domain: 'example.com',
|
|
759
|
+
authenticator_type: 'platform', // or 'cross-platform'
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
console.log(response.public_key_credential_creation_options);
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
#### Register WebAuthn Finish
|
|
766
|
+
|
|
767
|
+
```javascript
|
|
768
|
+
const response = await client.webauthn.register({
|
|
769
|
+
user_id: 'user-123',
|
|
770
|
+
public_key_credential: 'credential-from-client',
|
|
771
|
+
session_duration_minutes: 60,
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
console.log(response.webauthn_registration_id);
|
|
775
|
+
console.log(response.session_token);
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
#### Authenticate WebAuthn Start
|
|
779
|
+
|
|
780
|
+
```javascript
|
|
781
|
+
const response = await client.webauthn.authenticateStart({
|
|
782
|
+
domain: 'example.com',
|
|
783
|
+
user_id: 'user-123', // Optional
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
console.log(response.public_key_credential_request_options);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
#### Authenticate WebAuthn Finish
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
const response = await client.webauthn.authenticate({
|
|
793
|
+
public_key_credential: 'credential-from-client',
|
|
794
|
+
session_duration_minutes: 60,
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
console.log(response.user);
|
|
798
|
+
console.log(response.session_token);
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
#### Update WebAuthn
|
|
802
|
+
|
|
803
|
+
```javascript
|
|
804
|
+
const response = await client.webauthn.update({
|
|
805
|
+
webauthn_registration_id: 'webauthn-reg-id-123',
|
|
806
|
+
name: 'My Fingerprint',
|
|
807
|
+
});
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
---
|
|
811
|
+
|
|
812
|
+
### 8. TOTP (Time-based One-Time Passwords)
|
|
813
|
+
|
|
814
|
+
#### Create TOTP
|
|
815
|
+
|
|
816
|
+
```javascript
|
|
817
|
+
const response = await client.totps.create({
|
|
818
|
+
user_id: 'user-123',
|
|
819
|
+
expiration_minutes: 10,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
console.log(response.secret);
|
|
823
|
+
console.log(response.qr_code); // URL for QR code image
|
|
824
|
+
console.log(response.recovery_codes);
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
#### Authenticate TOTP
|
|
828
|
+
|
|
829
|
+
```javascript
|
|
830
|
+
const response = await client.totps.authenticate({
|
|
831
|
+
user_id: 'user-123',
|
|
832
|
+
totp_code: '123456',
|
|
833
|
+
session_duration_minutes: 60,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
console.log(response.session_token);
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
#### Get TOTPs
|
|
840
|
+
|
|
841
|
+
```javascript
|
|
842
|
+
const response = await client.totps.get({
|
|
843
|
+
user_id: 'user-123',
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
console.log(response.totps);
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
#### Recover TOTP
|
|
850
|
+
|
|
851
|
+
```javascript
|
|
852
|
+
const response = await client.totps.recoveryCodes({
|
|
853
|
+
user_id: 'user-123',
|
|
854
|
+
recovery_code: 'recovery-code-string',
|
|
855
|
+
session_duration_minutes: 60,
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
console.log(response.session_token);
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
### 9. Crypto Wallets (Web3)
|
|
864
|
+
|
|
865
|
+
#### Authenticate Wallet Start
|
|
866
|
+
|
|
867
|
+
```javascript
|
|
868
|
+
const response = await client.cryptoWallets.authenticateStart({
|
|
869
|
+
crypto_wallet_address: '0x1234...',
|
|
870
|
+
crypto_wallet_type: 'ethereum',
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
console.log(response.challenge);
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
#### Authenticate Wallet Finish
|
|
877
|
+
|
|
878
|
+
```javascript
|
|
879
|
+
const response = await client.cryptoWallets.authenticate({
|
|
880
|
+
crypto_wallet_address: '0x1234...',
|
|
881
|
+
crypto_wallet_type: 'ethereum',
|
|
882
|
+
signature: 'signed-challenge',
|
|
883
|
+
session_duration_minutes: 60,
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
console.log(response.user);
|
|
887
|
+
console.log(response.session_token);
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
---
|
|
891
|
+
|
|
892
|
+
### 10. M2M (Machine-to-Machine)
|
|
893
|
+
|
|
894
|
+
#### Authenticate M2M Token
|
|
895
|
+
|
|
896
|
+
```javascript
|
|
897
|
+
const response = await client.m2m.authenticateToken({
|
|
898
|
+
access_token: 'm2m-access-token',
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
console.log(response.member_id);
|
|
902
|
+
console.log(response.scopes);
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
#### Get M2M Client
|
|
906
|
+
|
|
907
|
+
```javascript
|
|
908
|
+
const response = await client.m2m.clients.get({
|
|
909
|
+
client_id: 'client-id-123',
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
console.log(response.m2m_client);
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## B2B API Surfaces
|
|
918
|
+
|
|
919
|
+
### 1. Organizations
|
|
920
|
+
|
|
921
|
+
#### Create Organization
|
|
922
|
+
|
|
923
|
+
```javascript
|
|
924
|
+
const response = await b2bClient.organizations.create({
|
|
925
|
+
organization_name: 'Acme Corp',
|
|
926
|
+
organization_slug: 'acme',
|
|
927
|
+
email_allowed_domains: ['acme.com'],
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
console.log(response.organization);
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
#### Get Organization
|
|
934
|
+
|
|
935
|
+
```javascript
|
|
936
|
+
const response = await b2bClient.organizations.get({
|
|
937
|
+
organization_id: 'org-123',
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
console.log(response.organization);
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
#### Update Organization
|
|
944
|
+
|
|
945
|
+
```javascript
|
|
946
|
+
const response = await b2bClient.organizations.update({
|
|
947
|
+
organization_id: 'org-123',
|
|
948
|
+
organization_name: 'Acme Corporation',
|
|
949
|
+
});
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
#### Delete Organization
|
|
953
|
+
|
|
954
|
+
```javascript
|
|
955
|
+
await b2bClient.organizations.delete({
|
|
956
|
+
organization_id: 'org-123',
|
|
957
|
+
});
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
---
|
|
961
|
+
|
|
962
|
+
### 2. Members
|
|
963
|
+
|
|
964
|
+
#### Create Member
|
|
965
|
+
|
|
966
|
+
```javascript
|
|
967
|
+
const response = await b2bClient.organizations.members.create({
|
|
968
|
+
organization_id: 'org-123',
|
|
969
|
+
email_address: 'member@acme.com',
|
|
970
|
+
name: 'John Doe',
|
|
971
|
+
is_breakglass: false,
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
console.log(response.member);
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
#### Get Member
|
|
978
|
+
|
|
979
|
+
```javascript
|
|
980
|
+
const response = await b2bClient.organizations.members.get({
|
|
981
|
+
organization_id: 'org-123',
|
|
982
|
+
member_id: 'member-123',
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
console.log(response.member);
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
#### Update Member
|
|
989
|
+
|
|
990
|
+
```javascript
|
|
991
|
+
const response = await b2bClient.organizations.members.update({
|
|
992
|
+
organization_id: 'org-123',
|
|
993
|
+
member_id: 'member-123',
|
|
994
|
+
name: 'Jane Doe',
|
|
995
|
+
});
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
#### Delete Member
|
|
999
|
+
|
|
1000
|
+
```javascript
|
|
1001
|
+
await b2bClient.organizations.members.delete({
|
|
1002
|
+
organization_id: 'org-123',
|
|
1003
|
+
member_id: 'member-123',
|
|
1004
|
+
});
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
### 3. B2B Magic Links
|
|
1010
|
+
|
|
1011
|
+
#### Send B2B Magic Link
|
|
1012
|
+
|
|
1013
|
+
```javascript
|
|
1014
|
+
const response = await b2bClient.magicLinks.email.loginOrSignup({
|
|
1015
|
+
organization_id: 'org-123',
|
|
1016
|
+
email_address: 'member@acme.com',
|
|
1017
|
+
login_redirect_url: 'https://acme.com/authenticate',
|
|
1018
|
+
signup_redirect_url: 'https://acme.com/authenticate',
|
|
1019
|
+
});
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
#### Authenticate B2B Magic Link
|
|
1023
|
+
|
|
1024
|
+
```javascript
|
|
1025
|
+
const response = await b2bClient.magicLinks.authenticate({
|
|
1026
|
+
magic_links_token: 'token-from-email',
|
|
1027
|
+
session_duration_minutes: 60,
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
console.log(response.member);
|
|
1031
|
+
console.log(response.organization);
|
|
1032
|
+
console.log(response.session_token);
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
### 4. B2B Sessions
|
|
1038
|
+
|
|
1039
|
+
#### Authenticate B2B Session
|
|
1040
|
+
|
|
1041
|
+
```javascript
|
|
1042
|
+
const response = await b2bClient.sessions.authenticate({
|
|
1043
|
+
session_token: 'session-token-here',
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
console.log(response.member);
|
|
1047
|
+
console.log(response.organization);
|
|
1048
|
+
console.log(response.session);
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
#### Authenticate B2B JWT
|
|
1052
|
+
|
|
1053
|
+
```javascript
|
|
1054
|
+
const response = await b2bClient.sessions.authenticateJwt({
|
|
1055
|
+
session_jwt: 'jwt-token-here',
|
|
1056
|
+
});
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
#### Get B2B Session
|
|
1060
|
+
|
|
1061
|
+
```javascript
|
|
1062
|
+
const response = await b2bClient.sessions.get({
|
|
1063
|
+
organization_id: 'org-123',
|
|
1064
|
+
member_id: 'member-123',
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
console.log(response.sessions);
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
#### Revoke B2B Session
|
|
1071
|
+
|
|
1072
|
+
```javascript
|
|
1073
|
+
await b2bClient.sessions.revoke({
|
|
1074
|
+
session_token: 'session-token-to-revoke',
|
|
1075
|
+
});
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
---
|
|
1079
|
+
|
|
1080
|
+
### 5. B2B OAuth
|
|
1081
|
+
|
|
1082
|
+
#### Start B2B OAuth
|
|
1083
|
+
|
|
1084
|
+
```javascript
|
|
1085
|
+
const response = await b2bClient.oauth.start({
|
|
1086
|
+
organization_id: 'org-123',
|
|
1087
|
+
provider: 'google',
|
|
1088
|
+
login_redirect_url: 'https://acme.com/authenticate',
|
|
1089
|
+
signup_redirect_url: 'https://acme.com/authenticate',
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
console.log(response.oauth_url);
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
#### Authenticate B2B OAuth
|
|
1096
|
+
|
|
1097
|
+
```javascript
|
|
1098
|
+
const response = await b2bClient.oauth.authenticate({
|
|
1099
|
+
oauth_token: 'token-from-callback',
|
|
1100
|
+
session_duration_minutes: 60,
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
console.log(response.member);
|
|
1104
|
+
console.log(response.organization);
|
|
1105
|
+
console.log(response.session_token);
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
---
|
|
1109
|
+
|
|
1110
|
+
### 6. B2B SMS OTP
|
|
1111
|
+
|
|
1112
|
+
#### Send B2B SMS OTP
|
|
1113
|
+
|
|
1114
|
+
```javascript
|
|
1115
|
+
const response = await b2bClient.otps.sms.send({
|
|
1116
|
+
organization_id: 'org-123',
|
|
1117
|
+
member_id: 'member-123',
|
|
1118
|
+
phone_number: '+15555555555',
|
|
1119
|
+
});
|
|
1120
|
+
```
|
|
1121
|
+
|
|
1122
|
+
#### Authenticate B2B SMS OTP
|
|
1123
|
+
|
|
1124
|
+
```javascript
|
|
1125
|
+
const response = await b2bClient.otps.sms.authenticate({
|
|
1126
|
+
organization_id: 'org-123',
|
|
1127
|
+
member_id: 'member-123',
|
|
1128
|
+
code: '123456',
|
|
1129
|
+
session_duration_minutes: 60,
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
console.log(response.member);
|
|
1133
|
+
console.log(response.session_token);
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
---
|
|
1137
|
+
|
|
1138
|
+
### 7. SSO (Single Sign-On)
|
|
1139
|
+
|
|
1140
|
+
#### Start SSO
|
|
1141
|
+
|
|
1142
|
+
```javascript
|
|
1143
|
+
const response = await b2bClient.sso.start({
|
|
1144
|
+
connection_id: 'sso-connection-123',
|
|
1145
|
+
login_redirect_url: 'https://acme.com/authenticate',
|
|
1146
|
+
signup_redirect_url: 'https://acme.com/authenticate',
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
console.log(response.sso_url);
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
#### Authenticate SSO
|
|
1153
|
+
|
|
1154
|
+
```javascript
|
|
1155
|
+
const response = await b2bClient.sso.authenticate({
|
|
1156
|
+
sso_token: 'token-from-sso-provider',
|
|
1157
|
+
session_duration_minutes: 60,
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
console.log(response.member);
|
|
1161
|
+
console.log(response.organization);
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
#### Get SSO Connections
|
|
1165
|
+
|
|
1166
|
+
```javascript
|
|
1167
|
+
const response = await b2bClient.sso.getConnections({
|
|
1168
|
+
organization_id: 'org-123',
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
console.log(response.saml_connections);
|
|
1172
|
+
console.log(response.oidc_connections);
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
#### Create SAML Connection
|
|
1176
|
+
|
|
1177
|
+
```javascript
|
|
1178
|
+
const response = await b2bClient.sso.saml.createConnection({
|
|
1179
|
+
organization_id: 'org-123',
|
|
1180
|
+
display_name: 'Acme SAML',
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
console.log(response.connection);
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
#### Update SAML Connection
|
|
1187
|
+
|
|
1188
|
+
```javascript
|
|
1189
|
+
const response = await b2bClient.sso.saml.updateConnection({
|
|
1190
|
+
organization_id: 'org-123',
|
|
1191
|
+
connection_id: 'saml-connection-123',
|
|
1192
|
+
idp_entity_id: 'https://idp.acme.com/entity',
|
|
1193
|
+
idp_sso_url: 'https://idp.acme.com/sso',
|
|
1194
|
+
attribute_mapping: {
|
|
1195
|
+
email: 'email',
|
|
1196
|
+
first_name: 'firstName',
|
|
1197
|
+
last_name: 'lastName',
|
|
1198
|
+
},
|
|
1199
|
+
x509_certificate: 'certificate-string',
|
|
1200
|
+
});
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
#### Create OIDC Connection
|
|
1204
|
+
|
|
1205
|
+
```javascript
|
|
1206
|
+
const response = await b2bClient.sso.oidc.createConnection({
|
|
1207
|
+
organization_id: 'org-123',
|
|
1208
|
+
display_name: 'Acme OIDC',
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
console.log(response.connection);
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
#### Update OIDC Connection
|
|
1215
|
+
|
|
1216
|
+
```javascript
|
|
1217
|
+
const response = await b2bClient.sso.oidc.updateConnection({
|
|
1218
|
+
organization_id: 'org-123',
|
|
1219
|
+
connection_id: 'oidc-connection-123',
|
|
1220
|
+
issuer: 'https://idp.acme.com',
|
|
1221
|
+
client_id: 'client-id',
|
|
1222
|
+
client_secret: 'client-secret',
|
|
1223
|
+
authorization_url: 'https://idp.acme.com/authorize',
|
|
1224
|
+
token_url: 'https://idp.acme.com/token',
|
|
1225
|
+
userinfo_url: 'https://idp.acme.com/userinfo',
|
|
1226
|
+
});
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
---
|
|
1230
|
+
|
|
1231
|
+
### 8. Discovery
|
|
1232
|
+
|
|
1233
|
+
Discovery allows users to find and join organizations.
|
|
1234
|
+
|
|
1235
|
+
#### Create Organization via Discovery
|
|
1236
|
+
|
|
1237
|
+
```javascript
|
|
1238
|
+
const response = await b2bClient.discovery.organizations.create({
|
|
1239
|
+
intermediate_session_token: 'token-from-discovery',
|
|
1240
|
+
organization_name: 'New Org',
|
|
1241
|
+
organization_slug: 'new-org',
|
|
1242
|
+
session_duration_minutes: 60,
|
|
1243
|
+
});
|
|
1244
|
+
|
|
1245
|
+
console.log(response.organization);
|
|
1246
|
+
console.log(response.session_token);
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
#### List Discovered Organizations
|
|
1250
|
+
|
|
1251
|
+
```javascript
|
|
1252
|
+
const response = await b2bClient.discovery.organizations.list({
|
|
1253
|
+
intermediate_session_token: 'token-from-discovery',
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
console.log(response.discovered_organizations);
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
---
|
|
1260
|
+
|
|
1261
|
+
### 9. RBAC (Role-Based Access Control)
|
|
1262
|
+
|
|
1263
|
+
#### Check Member Permissions
|
|
1264
|
+
|
|
1265
|
+
```javascript
|
|
1266
|
+
const response = await b2bClient.rbac.policy({
|
|
1267
|
+
organization_id: 'org-123',
|
|
1268
|
+
member_id: 'member-123',
|
|
1269
|
+
resource_id: 'document-123',
|
|
1270
|
+
action: 'read',
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
console.log(response.authorized);
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
---
|
|
1277
|
+
|
|
1278
|
+
## Error Handling
|
|
1279
|
+
|
|
1280
|
+
### Basic Error Handling
|
|
1281
|
+
|
|
1282
|
+
```javascript
|
|
1283
|
+
try {
|
|
1284
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
1285
|
+
email: 'user@example.com',
|
|
1286
|
+
});
|
|
1287
|
+
console.log(response.user_id);
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
console.error('Error:', error.error_type);
|
|
1290
|
+
console.error('Message:', error.error_message);
|
|
1291
|
+
console.error('URL:', error.error_url);
|
|
1292
|
+
}
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
### Detailed Error Handling
|
|
1296
|
+
|
|
1297
|
+
```javascript
|
|
1298
|
+
const response = await client.magicLinks.authenticate({
|
|
1299
|
+
token: token,
|
|
1300
|
+
}).catch((error) => {
|
|
1301
|
+
if (error.error_type === 'unable_to_auth_magic_link') {
|
|
1302
|
+
// Token invalid, expired, or already used
|
|
1303
|
+
return { error: 'Invalid or expired magic link' };
|
|
1304
|
+
} else if (error.error_type === 'invalid_token') {
|
|
1305
|
+
// Malformed token
|
|
1306
|
+
return { error: 'Invalid token format' };
|
|
1307
|
+
} else {
|
|
1308
|
+
// Other errors
|
|
1309
|
+
return { error: 'Authentication failed' };
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
### Common Error Types
|
|
1315
|
+
|
|
1316
|
+
- `unable_to_auth_magic_link` - Magic link token invalid, expired, or used
|
|
1317
|
+
- `invalid_token` - Token format is invalid
|
|
1318
|
+
- `session_not_found` - Session doesn't exist
|
|
1319
|
+
- `user_not_found` - User doesn't exist
|
|
1320
|
+
- `duplicate_email` - Email already exists
|
|
1321
|
+
- `invalid_credentials` - Password authentication failed
|
|
1322
|
+
- `rate_limit_exceeded` - Too many requests
|
|
1323
|
+
- `unauthorized` - API credentials invalid
|
|
1324
|
+
|
|
1325
|
+
---
|
|
1326
|
+
|
|
1327
|
+
## Express.js Integration Example
|
|
1328
|
+
|
|
1329
|
+
### Complete Auth Flow
|
|
1330
|
+
|
|
1331
|
+
```javascript
|
|
1332
|
+
const express = require('express');
|
|
1333
|
+
const stytch = require('stytch');
|
|
1334
|
+
const session = require('express-session');
|
|
1335
|
+
|
|
1336
|
+
const app = express();
|
|
1337
|
+
app.use(express.json());
|
|
1338
|
+
app.use(session({
|
|
1339
|
+
secret: 'your-session-secret',
|
|
1340
|
+
resave: false,
|
|
1341
|
+
saveUninitialized: false,
|
|
1342
|
+
}));
|
|
1343
|
+
|
|
1344
|
+
const client = new stytch.Client({
|
|
1345
|
+
project_id: process.env.STYTCH_PROJECT_ID,
|
|
1346
|
+
secret: process.env.STYTCH_SECRET,
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
// Login endpoint
|
|
1350
|
+
app.post('/login', async (req, res) => {
|
|
1351
|
+
try {
|
|
1352
|
+
const { email } = req.body;
|
|
1353
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
1354
|
+
email,
|
|
1355
|
+
login_magic_link_url: `${process.env.BASE_URL}/authenticate`,
|
|
1356
|
+
signup_magic_link_url: `${process.env.BASE_URL}/authenticate`,
|
|
1357
|
+
});
|
|
1358
|
+
res.json({ success: true, user_id: response.user_id });
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
res.status(400).json({ error: error.error_message });
|
|
1361
|
+
}
|
|
1362
|
+
});
|
|
1363
|
+
|
|
1364
|
+
// Authentication callback
|
|
1365
|
+
app.get('/authenticate', async (req, res) => {
|
|
1366
|
+
try {
|
|
1367
|
+
const { token } = req.query;
|
|
1368
|
+
const response = await client.magicLinks.authenticate({
|
|
1369
|
+
token,
|
|
1370
|
+
session_duration_minutes: 60,
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
req.session.stytchSessionToken = response.session_token;
|
|
1374
|
+
req.session.userId = response.user.user_id;
|
|
1375
|
+
|
|
1376
|
+
res.redirect('/dashboard');
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
res.status(400).send('Authentication failed');
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
// Protected route middleware
|
|
1383
|
+
async function authenticateSession(req, res, next) {
|
|
1384
|
+
const sessionToken = req.session.stytchSessionToken;
|
|
1385
|
+
|
|
1386
|
+
if (!sessionToken) {
|
|
1387
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
try {
|
|
1391
|
+
const response = await client.sessions.authenticate({
|
|
1392
|
+
session_token: sessionToken,
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
req.session.stytchSessionToken = response.session_token;
|
|
1396
|
+
req.user = response.user;
|
|
1397
|
+
next();
|
|
1398
|
+
} catch (error) {
|
|
1399
|
+
req.session.destroy();
|
|
1400
|
+
res.status(401).json({ error: 'Session expired' });
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// Protected route
|
|
1405
|
+
app.get('/dashboard', authenticateSession, (req, res) => {
|
|
1406
|
+
res.json({ user: req.user });
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
// Logout
|
|
1410
|
+
app.post('/logout', async (req, res) => {
|
|
1411
|
+
try {
|
|
1412
|
+
await client.sessions.revoke({
|
|
1413
|
+
session_token: req.session.stytchSessionToken,
|
|
1414
|
+
});
|
|
1415
|
+
req.session.destroy();
|
|
1416
|
+
res.json({ success: true });
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
res.status(400).json({ error: error.error_message });
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
app.listen(3000);
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
---
|
|
1426
|
+
|
|
1427
|
+
## TypeScript Support
|
|
1428
|
+
|
|
1429
|
+
The SDK includes full TypeScript definitions.
|
|
1430
|
+
|
|
1431
|
+
### TypeScript Example
|
|
1432
|
+
|
|
1433
|
+
```typescript
|
|
1434
|
+
import * as stytch from 'stytch';
|
|
1435
|
+
|
|
1436
|
+
const client = new stytch.Client({
|
|
1437
|
+
project_id: process.env.STYTCH_PROJECT_ID!,
|
|
1438
|
+
secret: process.env.STYTCH_SECRET!,
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
async function loginUser(email: string): Promise<string> {
|
|
1442
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
1443
|
+
email,
|
|
1444
|
+
});
|
|
1445
|
+
return response.user_id;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async function authenticateToken(token: string): Promise<stytch.User> {
|
|
1449
|
+
const response = await client.magicLinks.authenticate({
|
|
1450
|
+
token,
|
|
1451
|
+
});
|
|
1452
|
+
return response.user;
|
|
1453
|
+
}
|
|
1454
|
+
```
|
|
1455
|
+
|
|
1456
|
+
### Request/Response Types
|
|
1457
|
+
|
|
1458
|
+
Types follow the pattern: `$Vertical$Product$Method(Request|Response)`
|
|
1459
|
+
|
|
1460
|
+
```typescript
|
|
1461
|
+
import type {
|
|
1462
|
+
MagicLinksEmailLoginOrCreateRequest,
|
|
1463
|
+
MagicLinksEmailLoginOrCreateResponse,
|
|
1464
|
+
MagicLinksAuthenticateRequest,
|
|
1465
|
+
MagicLinksAuthenticateResponse,
|
|
1466
|
+
} from 'stytch';
|
|
1467
|
+
|
|
1468
|
+
const request: MagicLinksEmailLoginOrCreateRequest = {
|
|
1469
|
+
email: 'user@example.com',
|
|
1470
|
+
login_magic_link_url: 'https://example.com/authenticate',
|
|
1471
|
+
};
|
|
1472
|
+
|
|
1473
|
+
const response: MagicLinksEmailLoginOrCreateResponse =
|
|
1474
|
+
await client.magicLinks.email.loginOrCreate(request);
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
---
|
|
1478
|
+
|
|
1479
|
+
## Testing
|
|
1480
|
+
|
|
1481
|
+
### Using Test Environment
|
|
1482
|
+
|
|
1483
|
+
```javascript
|
|
1484
|
+
const client = new stytch.Client({
|
|
1485
|
+
project_id: process.env.STYTCH_TEST_PROJECT_ID,
|
|
1486
|
+
secret: process.env.STYTCH_TEST_SECRET,
|
|
1487
|
+
environment: 'test',
|
|
1488
|
+
});
|
|
1489
|
+
```
|
|
1490
|
+
|
|
1491
|
+
### Test Mode Magic Links
|
|
1492
|
+
|
|
1493
|
+
In test mode, use these special test emails:
|
|
1494
|
+
- `sandbox@stytch.com` - Always succeeds
|
|
1495
|
+
|
|
1496
|
+
### Mock Testing
|
|
1497
|
+
|
|
1498
|
+
```javascript
|
|
1499
|
+
const mockClient = {
|
|
1500
|
+
magicLinks: {
|
|
1501
|
+
email: {
|
|
1502
|
+
loginOrCreate: jest.fn().mockResolvedValue({
|
|
1503
|
+
user_id: 'user-test-123',
|
|
1504
|
+
email_id: 'email-test-123',
|
|
1505
|
+
}),
|
|
1506
|
+
},
|
|
1507
|
+
authenticate: jest.fn().mockResolvedValue({
|
|
1508
|
+
user: {
|
|
1509
|
+
user_id: 'user-test-123',
|
|
1510
|
+
emails: [{ email: 'test@example.com' }],
|
|
1511
|
+
},
|
|
1512
|
+
session_token: 'test-session-token',
|
|
1513
|
+
}),
|
|
1514
|
+
},
|
|
1515
|
+
};
|
|
1516
|
+
```
|
|
1517
|
+
|
|
1518
|
+
---
|
|
1519
|
+
|
|
1520
|
+
## Webhooks
|
|
1521
|
+
|
|
1522
|
+
### Verify Webhook Signature
|
|
1523
|
+
|
|
1524
|
+
```javascript
|
|
1525
|
+
const crypto = require('crypto');
|
|
1526
|
+
|
|
1527
|
+
function verifyWebhookSignature(payload, signature, secret) {
|
|
1528
|
+
const expectedSignature = crypto
|
|
1529
|
+
.createHmac('sha256', secret)
|
|
1530
|
+
.update(payload)
|
|
1531
|
+
.digest('hex');
|
|
1532
|
+
|
|
1533
|
+
return signature === expectedSignature;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Express webhook endpoint
|
|
1537
|
+
app.post('/webhooks/stytch', express.raw({ type: 'application/json' }), (req, res) => {
|
|
1538
|
+
const signature = req.headers['stytch-signature'];
|
|
1539
|
+
const isValid = verifyWebhookSignature(
|
|
1540
|
+
req.body,
|
|
1541
|
+
signature,
|
|
1542
|
+
process.env.STYTCH_WEBHOOK_SECRET
|
|
1543
|
+
);
|
|
1544
|
+
|
|
1545
|
+
if (!isValid) {
|
|
1546
|
+
return res.status(401).send('Invalid signature');
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
const event = JSON.parse(req.body);
|
|
1550
|
+
console.log('Webhook event:', event.event_type);
|
|
1551
|
+
|
|
1552
|
+
res.json({ received: true });
|
|
1553
|
+
});
|
|
1554
|
+
```
|
|
1555
|
+
|
|
1556
|
+
### Webhook Event Types
|
|
1557
|
+
|
|
1558
|
+
- `user.created` - New user created
|
|
1559
|
+
- `user.updated` - User data updated
|
|
1560
|
+
- `user.deleted` - User deleted
|
|
1561
|
+
- `session.created` - New session created
|
|
1562
|
+
- `session.revoked` - Session revoked
|
|
1563
|
+
- `magic_link.sent` - Magic link sent
|
|
1564
|
+
- `otp.sent` - OTP sent
|
|
1565
|
+
- `password.strength_check_failed` - Weak password detected
|
|
1566
|
+
|
|
1567
|
+
---
|
|
1568
|
+
|
|
1569
|
+
## Session Management Patterns
|
|
1570
|
+
|
|
1571
|
+
### Session Token in Cookie
|
|
1572
|
+
|
|
1573
|
+
```javascript
|
|
1574
|
+
app.get('/authenticate', async (req, res) => {
|
|
1575
|
+
const { token } = req.query;
|
|
1576
|
+
const response = await client.magicLinks.authenticate({
|
|
1577
|
+
token,
|
|
1578
|
+
session_duration_minutes: 60,
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
res.cookie('stytch_session', response.session_token, {
|
|
1582
|
+
httpOnly: true,
|
|
1583
|
+
secure: true,
|
|
1584
|
+
maxAge: 60 * 60 * 1000, // 1 hour
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
res.redirect('/dashboard');
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
// Middleware
|
|
1591
|
+
async function authenticate(req, res, next) {
|
|
1592
|
+
const sessionToken = req.cookies.stytch_session;
|
|
1593
|
+
if (!sessionToken) {
|
|
1594
|
+
return res.status(401).json({ error: 'Not authenticated' });
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
try {
|
|
1598
|
+
const response = await client.sessions.authenticate({
|
|
1599
|
+
session_token: sessionToken,
|
|
1600
|
+
});
|
|
1601
|
+
req.user = response.user;
|
|
1602
|
+
next();
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
res.status(401).json({ error: 'Invalid session' });
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
### Session Token in Authorization Header
|
|
1610
|
+
|
|
1611
|
+
```javascript
|
|
1612
|
+
// Middleware
|
|
1613
|
+
async function authenticate(req, res, next) {
|
|
1614
|
+
const authHeader = req.headers.authorization;
|
|
1615
|
+
if (!authHeader) {
|
|
1616
|
+
return res.status(401).json({ error: 'No token provided' });
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
const sessionToken = authHeader.replace('Bearer ', '');
|
|
1620
|
+
|
|
1621
|
+
try {
|
|
1622
|
+
const response = await client.sessions.authenticate({
|
|
1623
|
+
session_token: sessionToken,
|
|
1624
|
+
});
|
|
1625
|
+
req.user = response.user;
|
|
1626
|
+
next();
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
res.status(401).json({ error: 'Invalid token' });
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
---
|
|
1634
|
+
|
|
1635
|
+
## Advanced Use Cases
|
|
1636
|
+
|
|
1637
|
+
### Multi-Factor Authentication Flow
|
|
1638
|
+
|
|
1639
|
+
```javascript
|
|
1640
|
+
// Step 1: Primary authentication (password)
|
|
1641
|
+
app.post('/login', async (req, res) => {
|
|
1642
|
+
const { email, password } = req.body;
|
|
1643
|
+
|
|
1644
|
+
try {
|
|
1645
|
+
const response = await client.passwords.authenticate({
|
|
1646
|
+
email,
|
|
1647
|
+
password,
|
|
1648
|
+
session_duration_minutes: 5, // Short session for MFA
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
// Return intermediate session
|
|
1652
|
+
res.json({
|
|
1653
|
+
requires_mfa: true,
|
|
1654
|
+
intermediate_session_token: response.session_token,
|
|
1655
|
+
user_id: response.user_id,
|
|
1656
|
+
});
|
|
1657
|
+
} catch (error) {
|
|
1658
|
+
res.status(401).json({ error: 'Invalid credentials' });
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
// Step 2: MFA with TOTP
|
|
1663
|
+
app.post('/verify-totp', async (req, res) => {
|
|
1664
|
+
const { user_id, totp_code } = req.body;
|
|
1665
|
+
|
|
1666
|
+
try {
|
|
1667
|
+
const response = await client.totps.authenticate({
|
|
1668
|
+
user_id,
|
|
1669
|
+
totp_code,
|
|
1670
|
+
session_duration_minutes: 60, // Full session after MFA
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
res.json({
|
|
1674
|
+
session_token: response.session_token,
|
|
1675
|
+
user: response.user,
|
|
1676
|
+
});
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
res.status(401).json({ error: 'Invalid TOTP code' });
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
```
|
|
1682
|
+
|
|
1683
|
+
### Account Linking
|
|
1684
|
+
|
|
1685
|
+
```javascript
|
|
1686
|
+
// Link OAuth account to existing user
|
|
1687
|
+
app.post('/link-oauth', authenticateSession, async (req, res) => {
|
|
1688
|
+
const { oauth_token } = req.body;
|
|
1689
|
+
|
|
1690
|
+
try {
|
|
1691
|
+
const response = await client.oauth.authenticate({
|
|
1692
|
+
token: oauth_token,
|
|
1693
|
+
session_token: req.session.stytchSessionToken,
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
res.json({ success: true, user: response.user });
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
res.status(400).json({ error: error.error_message });
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
```
|
|
1702
|
+
|
|
1703
|
+
### Custom Email Templates
|
|
1704
|
+
|
|
1705
|
+
```javascript
|
|
1706
|
+
const response = await client.magicLinks.email.loginOrCreate({
|
|
1707
|
+
email: 'user@example.com',
|
|
1708
|
+
login_magic_link_url: 'https://example.com/authenticate',
|
|
1709
|
+
login_template_id: 'custom-login-template-id',
|
|
1710
|
+
signup_template_id: 'custom-signup-template-id',
|
|
1711
|
+
});
|
|
1712
|
+
```
|
|
1713
|
+
|
|
1714
|
+
### IP and User Agent Matching
|
|
1715
|
+
|
|
1716
|
+
```javascript
|
|
1717
|
+
const response = await client.magicLinks.authenticate({
|
|
1718
|
+
token,
|
|
1719
|
+
attributes: {
|
|
1720
|
+
ip_address: req.ip,
|
|
1721
|
+
user_agent: req.headers['user-agent'],
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1724
|
+
```
|
|
1725
|
+
|
|
1726
|
+
---
|
|
1727
|
+
|
|
1728
|
+
## Rate Limiting
|
|
1729
|
+
|
|
1730
|
+
Stytch implements rate limiting on authentication endpoints. Handle rate limit errors:
|
|
1731
|
+
|
|
1732
|
+
```javascript
|
|
1733
|
+
try {
|
|
1734
|
+
await client.otps.sms.send({ phone_number });
|
|
1735
|
+
} catch (error) {
|
|
1736
|
+
if (error.error_type === 'rate_limit_exceeded') {
|
|
1737
|
+
const retryAfter = error.retry_after; // seconds
|
|
1738
|
+
res.status(429).json({
|
|
1739
|
+
error: 'Too many requests',
|
|
1740
|
+
retry_after: retryAfter,
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
```
|
|
1745
|
+
|
|
1746
|
+
---
|
|
1747
|
+
|
|
1748
|
+
## Migration from Other Auth Systems
|
|
1749
|
+
|
|
1750
|
+
### Import Users with Passwords
|
|
1751
|
+
|
|
1752
|
+
```javascript
|
|
1753
|
+
// Migrate user from bcrypt-based system
|
|
1754
|
+
await client.passwords.migrate({
|
|
1755
|
+
email: 'user@example.com',
|
|
1756
|
+
hash: '$2a$10$existing_bcrypt_hash',
|
|
1757
|
+
hash_type: 'bcrypt',
|
|
1758
|
+
name: {
|
|
1759
|
+
first_name: 'John',
|
|
1760
|
+
last_name: 'Doe',
|
|
1761
|
+
},
|
|
1762
|
+
});
|
|
1763
|
+
```
|
|
1764
|
+
|
|
1765
|
+
### Supported Hash Types
|
|
1766
|
+
|
|
1767
|
+
- `bcrypt`
|
|
1768
|
+
- `md_5`
|
|
1769
|
+
- `argon_2i`
|
|
1770
|
+
- `argon_2id`
|
|
1771
|
+
- `sha_1`
|
|
1772
|
+
- `scrypt`
|
|
1773
|
+
- `phpass`
|
|
1774
|
+
- `pbkdf_2`
|
|
1775
|
+
|
|
1776
|
+
---
|
|
1777
|
+
|
|
1778
|
+
## Async/Await vs Promises
|
|
1779
|
+
|
|
1780
|
+
All SDK methods support both patterns:
|
|
1781
|
+
|
|
1782
|
+
### Async/Await
|
|
1783
|
+
|
|
1784
|
+
```javascript
|
|
1785
|
+
async function authenticateUser(token) {
|
|
1786
|
+
const response = await client.magicLinks.authenticate({ token });
|
|
1787
|
+
return response.user;
|
|
1788
|
+
}
|
|
1789
|
+
```
|
|
1790
|
+
|
|
1791
|
+
### Promises
|
|
1792
|
+
|
|
1793
|
+
```javascript
|
|
1794
|
+
function authenticateUser(token) {
|
|
1795
|
+
return client.magicLinks.authenticate({ token })
|
|
1796
|
+
.then(response => response.user)
|
|
1797
|
+
.catch(error => {
|
|
1798
|
+
console.error(error);
|
|
1799
|
+
throw error;
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
---
|
|
1805
|
+
|
|
1806
|
+
## Resources
|
|
1807
|
+
|
|
1808
|
+
- **Documentation:** https://stytch.com/docs
|
|
1809
|
+
- **API Reference:** https://stytch.com/docs/api
|
|
1810
|
+
- **GitHub:** https://github.com/stytchauth/stytch-node
|
|
1811
|
+
- **npm Package:** https://www.npmjs.com/package/stytch
|
|
1812
|
+
- **Dashboard:** https://stytch.com/dashboard
|
|
1813
|
+
- **Support:** support@stytch.com
|