@crosspost/sdk 0.1.2 → 0.1.3
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 +287 -176
- package/dist/index.cjs +438 -94
- package/dist/index.d.cts +193 -12
- package/dist/index.d.ts +193 -12
- package/dist/index.js +418 -92
- package/package.json +1 -1
- package/src/api/activity.ts +77 -0
- package/src/api/system.ts +55 -0
- package/src/core/client.ts +19 -16
- package/src/core/config.ts +4 -4
- package/src/core/request.ts +120 -94
- package/src/index.ts +25 -6
- package/src/utils/cookie.ts +21 -5
- package/src/utils/error-utils.ts +310 -0
package/README.md
CHANGED
@@ -2,126 +2,262 @@
|
|
2
2
|
|
3
3
|
SDK for interacting with the Crosspost API.
|
4
4
|
|
5
|
-
## Overview
|
6
|
-
|
7
|
-
This package provides a client for interacting with the Crosspost API, allowing you to easily
|
8
|
-
integrate social media posting capabilities into your applications. The SDK is designed to be
|
9
|
-
flexible and easy to use, with support for multiple authentication methods and platforms.
|
10
|
-
|
11
|
-
## Features
|
12
|
-
|
13
|
-
- Unified client for all supported platforms
|
14
|
-
- Flexible authentication:
|
15
|
-
- Direct `nearAuthData` injection
|
16
|
-
- Automatic cookie-based authentication (`__crosspost_auth`)
|
17
|
-
- Explicit authentication via `setAuthentication` method
|
18
|
-
- Platform-specific clients (Twitter, with more to come)
|
19
|
-
- Type-safe request/response handling using `@crosspost/types`
|
20
|
-
- Comprehensive error handling with specific `ApiError` and `PlatformError` types
|
21
|
-
- CSRF protection via Double Submit Cookie pattern
|
22
|
-
|
23
5
|
## Installation
|
24
6
|
|
25
7
|
```bash
|
26
8
|
bun install @crosspost/sdk
|
27
9
|
```
|
28
10
|
|
29
|
-
##
|
30
|
-
|
31
|
-
### Initializing the SDK
|
32
|
-
|
33
|
-
The SDK can be initialized in several ways depending on how you manage authentication:
|
34
|
-
|
35
|
-
**1. Cookie-Based Authentication (Recommended for Browsers)**
|
36
|
-
|
37
|
-
If the user has previously authenticated via the API, the SDK can automatically use the
|
38
|
-
authentication data stored in the `__crosspost_auth` cookie.
|
11
|
+
## Quick Start
|
39
12
|
|
40
13
|
```typescript
|
41
|
-
import { CrosspostClient } from '@crosspost/sdk';
|
14
|
+
import { ApiError, CrosspostClient, isAuthError, PlatformError } from '@crosspost/sdk';
|
42
15
|
|
43
|
-
//
|
16
|
+
// Initialize the client with direct authentication
|
44
17
|
const client = new CrosspostClient({
|
45
18
|
baseUrl: 'https://your-crosspost-api.com', // Optional: Defaults to official API
|
19
|
+
nearAuthData: {
|
20
|
+
accountId: 'your-account.near',
|
21
|
+
publicKey: 'ed25519:...',
|
22
|
+
signature: '...',
|
23
|
+
message: '...',
|
24
|
+
},
|
46
25
|
});
|
47
26
|
|
48
|
-
//
|
49
|
-
|
50
|
-
|
27
|
+
// Or initialize with cookie-based authentication (will auto-load from cookie if available)
|
28
|
+
const cookieClient = new CrosspostClient({
|
29
|
+
baseUrl: 'https://your-crosspost-api.com',
|
30
|
+
// No nearAuthData provided - will check for cookie
|
31
|
+
});
|
51
32
|
|
52
|
-
|
33
|
+
// Set authentication explicitly (also stores in secure cookie)
|
34
|
+
await cookieClient.setAuthentication({
|
35
|
+
accountId: 'your-account.near',
|
36
|
+
publicKey: 'ed25519:...',
|
37
|
+
signature: '...',
|
38
|
+
message: '...',
|
39
|
+
});
|
53
40
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
41
|
+
// Check if client is authenticated
|
42
|
+
if (cookieClient.isAuthenticated()) {
|
43
|
+
console.log('Client is authenticated');
|
44
|
+
}
|
58
45
|
|
59
|
-
|
46
|
+
// NEAR Account Authorization
|
47
|
+
async function authorizeNearAccount() {
|
48
|
+
try {
|
49
|
+
// Authorize with NEAR account
|
50
|
+
const authResponse = await client.auth.authorizeNearAccount();
|
51
|
+
console.log('NEAR authorization successful');
|
52
|
+
console.log('Account ID:', authResponse.accountId);
|
53
|
+
console.log('Status:', authResponse.status);
|
54
|
+
console.log('Connected platforms:', authResponse.connectedPlatforms);
|
55
|
+
return true;
|
56
|
+
} catch (error) {
|
57
|
+
console.error('NEAR authorization failed');
|
58
|
+
if (error instanceof ApiError) {
|
59
|
+
console.error('Error code:', error.code);
|
60
|
+
console.error('Status:', error.status);
|
61
|
+
console.error('Details:', error.details);
|
62
|
+
console.error('Recoverable:', error.recoverable);
|
63
|
+
}
|
64
|
+
return false;
|
65
|
+
}
|
66
|
+
}
|
60
67
|
|
61
|
-
|
62
|
-
|
68
|
+
// Unauthorize NEAR Account
|
69
|
+
async function unauthorizeNearAccount() {
|
70
|
+
try {
|
71
|
+
// Unauthorize NEAR account (removes all platform connections)
|
72
|
+
const response = await client.auth.unauthorizeNearAccount();
|
73
|
+
console.log('NEAR account unauthorized');
|
74
|
+
console.log('Status:', response.status);
|
75
|
+
console.log('Message:', response.message);
|
76
|
+
return true;
|
77
|
+
} catch (error) {
|
78
|
+
console.error('Failed to unauthorize NEAR account');
|
79
|
+
if (error instanceof ApiError) {
|
80
|
+
console.error('Error code:', error.code);
|
81
|
+
console.error('Status:', error.status);
|
82
|
+
console.error('Details:', error.details);
|
83
|
+
}
|
84
|
+
return false;
|
85
|
+
}
|
86
|
+
}
|
63
87
|
|
64
|
-
|
65
|
-
|
66
|
-
|
88
|
+
// Revoke Platform Authorization
|
89
|
+
async function revokePlatformAuth(platform) {
|
90
|
+
try {
|
91
|
+
// Revoke specific platform authorization
|
92
|
+
const response = await client.auth.revokeAuth(platform);
|
93
|
+
console.log(`${platform} authorization revoked`);
|
94
|
+
console.log('Status:', response.status);
|
95
|
+
console.log('Platform:', response.platform);
|
96
|
+
console.log('Message:', response.message);
|
97
|
+
return true;
|
98
|
+
} catch (error) {
|
99
|
+
console.error(`Failed to revoke ${platform} authorization`);
|
100
|
+
if (error instanceof ApiError) {
|
101
|
+
console.error('Error code:', error.code);
|
102
|
+
console.error('Status:', error.status);
|
103
|
+
console.error('Details:', error.details);
|
104
|
+
}
|
105
|
+
return false;
|
106
|
+
}
|
107
|
+
}
|
67
108
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
109
|
+
// Example usage
|
110
|
+
async function createPost() {
|
111
|
+
try {
|
112
|
+
const response = await client.post.createPost({
|
113
|
+
content: {
|
114
|
+
text: 'Hello from Crosspost SDK!',
|
115
|
+
},
|
116
|
+
});
|
117
|
+
console.log('Post created successfully');
|
118
|
+
console.log('Post ID:', response.id);
|
119
|
+
console.log('Platform:', response.platform);
|
120
|
+
console.log('URL:', response.url);
|
121
|
+
console.log('Created at:', response.createdAt);
|
122
|
+
} catch (error) {
|
123
|
+
// Check if it's an authentication error
|
124
|
+
if (isAuthError(error)) {
|
125
|
+
console.error('Authentication required. Attempting to authorize...');
|
126
|
+
const authorized = await authorizeNearAccount();
|
127
|
+
if (authorized) {
|
128
|
+
// Retry the operation
|
129
|
+
return createPost();
|
130
|
+
}
|
131
|
+
} else {
|
132
|
+
// Handle other error types
|
133
|
+
console.error('Error creating post:', error);
|
134
|
+
if (error instanceof ApiError) {
|
135
|
+
console.error('Error code:', error.code);
|
136
|
+
console.error('Status:', error.status);
|
137
|
+
console.error('Details:', error.details);
|
138
|
+
console.error('Recoverable:', error.recoverable);
|
139
|
+
} else if (error instanceof PlatformError) {
|
140
|
+
console.error('Platform:', error.platform);
|
141
|
+
console.error('Error code:', error.code);
|
142
|
+
console.error('Original error:', error.originalError);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
```
|
77
148
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
149
|
+
## API Reference
|
150
|
+
|
151
|
+
### CrosspostClient
|
152
|
+
|
153
|
+
```typescript
|
154
|
+
constructor(config?: {
|
155
|
+
baseUrl?: string;
|
156
|
+
nearAuthData?: NearAuthData;
|
157
|
+
timeout?: number;
|
158
|
+
retries?: number;
|
159
|
+
})
|
82
160
|
```
|
83
161
|
|
84
|
-
|
162
|
+
#### Methods
|
163
|
+
|
164
|
+
- `setAuthentication(nearAuthData: NearAuthData): Promise<void>` - Sets authentication data
|
165
|
+
- `isAuthenticated(): boolean` - Checks if client is authenticated
|
166
|
+
|
167
|
+
### Auth API (client.auth)
|
168
|
+
|
169
|
+
- `authorizeNearAccount(): Promise<NearAuthorizationResponse>` - Authorizes NEAR account
|
170
|
+
- `unauthorizeNearAccount(): Promise<NearAuthorizationResponse>` - Unauthorizes NEAR account
|
171
|
+
- `getNearAuthorizationStatus(): Promise<NearAuthorizationResponse>` - Checks authorization status
|
172
|
+
- `loginToPlatform(platform, options?): Promise<EnhancedApiResponse<any>>` - Initiates OAuth flow
|
173
|
+
- `refreshToken(platform): Promise<EnhancedApiResponse<any>>` - Refreshes platform token
|
174
|
+
- `refreshProfile(platform): Promise<EnhancedApiResponse<any>>` - Refreshes user profile
|
175
|
+
- `getAuthStatus(platform): Promise<AuthStatusResponse>` - Gets authentication status
|
176
|
+
- `revokeAuth(platform): Promise<AuthRevokeResponse>` - Revokes platform access
|
177
|
+
- `getConnectedAccounts(): Promise<ConnectedAccountsResponse>` - Lists connected accounts
|
178
|
+
|
179
|
+
### Post API (client.post)
|
180
|
+
|
181
|
+
- `createPost(request): Promise<CreatePostResponse>` - Creates a new post
|
182
|
+
- `repost(request): Promise<RepostResponse>` - Reposts an existing post
|
183
|
+
- `quotePost(request): Promise<QuotePostResponse>` - Quotes an existing post
|
184
|
+
- `replyToPost(request): Promise<ReplyToPostResponse>` - Replies to a post
|
185
|
+
- `likePost(request): Promise<LikePostResponse>` - Likes a post
|
186
|
+
- `unlikePost(request): Promise<UnlikePostResponse>` - Unlikes a post
|
187
|
+
- `deletePost(request): Promise<DeletePostResponse>` - Deletes a post
|
188
|
+
|
189
|
+
### Activity API (client.activity)
|
190
|
+
|
191
|
+
- `getLeaderboard(options): Promise<LeaderboardResponse>` - Gets activity leaderboard
|
192
|
+
- `getAccountActivity(signerId, options): Promise<AccountActivityResponse>` - Gets account activity
|
193
|
+
- `getAccountPosts(signerId, options): Promise<AccountPostsResponse>` - Gets account posts
|
85
194
|
|
86
|
-
|
87
|
-
|
88
|
-
|
195
|
+
### System API (client.system)
|
196
|
+
|
197
|
+
- `getRateLimits(): Promise<RateLimitsResponse>` - Gets all rate limits
|
198
|
+
- `getEndpointRateLimit(endpoint): Promise<EndpointRateLimitResponse>` - Gets endpoint rate limit
|
199
|
+
- `getHealthStatus(): Promise<HealthStatusResponse>` - Gets API health status
|
200
|
+
|
201
|
+
### Error Handling Utilities
|
89
202
|
|
90
203
|
```typescript
|
91
|
-
import {
|
92
|
-
|
204
|
+
import {
|
205
|
+
apiWrapper,
|
206
|
+
enrichErrorWithContext,
|
207
|
+
getErrorDetails,
|
208
|
+
getErrorMessage,
|
209
|
+
isAuthError,
|
210
|
+
isContentError,
|
211
|
+
isMediaError,
|
212
|
+
isNetworkError,
|
213
|
+
isPlatformError,
|
214
|
+
isPostError,
|
215
|
+
isRateLimitError,
|
216
|
+
isRecoverableError,
|
217
|
+
isValidationError,
|
218
|
+
} from '@crosspost/sdk';
|
219
|
+
|
220
|
+
// Check error types
|
221
|
+
if (isAuthError(error)) {
|
222
|
+
// Handle authentication errors
|
223
|
+
}
|
93
224
|
|
94
|
-
|
95
|
-
|
225
|
+
// Get user-friendly error message
|
226
|
+
const message = getErrorMessage(error, 'Default message');
|
227
|
+
|
228
|
+
// Get error details
|
229
|
+
const details = getErrorDetails(error);
|
230
|
+
|
231
|
+
// Add context to errors
|
232
|
+
const enrichedError = enrichErrorWithContext(error, {
|
233
|
+
operation: 'createPost',
|
234
|
+
timestamp: Date.now(),
|
96
235
|
});
|
97
236
|
|
98
|
-
//
|
99
|
-
|
100
|
-
|
101
|
-
//
|
102
|
-
await
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
console.error('Authentication failed:', error);
|
107
|
-
}
|
108
|
-
}
|
237
|
+
// Wrap API calls with error handling
|
238
|
+
const result = await apiWrapper(
|
239
|
+
async () => {
|
240
|
+
// API call implementation
|
241
|
+
return await fetch('/api/endpoint');
|
242
|
+
},
|
243
|
+
{ operation: 'fetchData' }, // Optional context
|
244
|
+
);
|
109
245
|
```
|
110
246
|
|
111
|
-
|
247
|
+
## Usage Examples
|
248
|
+
|
249
|
+
### Creating a Post
|
112
250
|
|
113
251
|
```typescript
|
114
|
-
// Create a post
|
115
|
-
const
|
252
|
+
// Create a simple text post
|
253
|
+
const textPostResponse = await client.post.createPost({
|
116
254
|
content: {
|
117
255
|
text: 'Hello from Crosspost SDK!',
|
118
256
|
},
|
119
257
|
});
|
120
258
|
|
121
|
-
console.log(`Post created with ID: ${createPostResponse.id}`);
|
122
|
-
|
123
259
|
// Create a post with media
|
124
|
-
const
|
260
|
+
const mediaPostResponse = await client.post.createPost({
|
125
261
|
content: {
|
126
262
|
text: 'Check out this image!',
|
127
263
|
media: [
|
@@ -132,19 +268,23 @@ const createPostWithMediaResponse = await client.twitter.createPost({
|
|
132
268
|
],
|
133
269
|
},
|
134
270
|
});
|
271
|
+
```
|
272
|
+
|
273
|
+
### Post Interactions
|
135
274
|
|
275
|
+
```typescript
|
136
276
|
// Like a post
|
137
|
-
await client.
|
277
|
+
await client.post.likePost({
|
138
278
|
postId: '1234567890',
|
139
279
|
});
|
140
280
|
|
141
|
-
// Repost
|
142
|
-
await client.
|
281
|
+
// Repost
|
282
|
+
await client.post.repost({
|
143
283
|
postId: '1234567890',
|
144
284
|
});
|
145
285
|
|
146
286
|
// Reply to a post
|
147
|
-
await client.
|
287
|
+
await client.post.replyToPost({
|
148
288
|
postId: '1234567890',
|
149
289
|
content: {
|
150
290
|
text: 'This is a reply!',
|
@@ -152,122 +292,93 @@ await client.twitter.reply({
|
|
152
292
|
});
|
153
293
|
|
154
294
|
// Delete a post
|
155
|
-
await client.
|
295
|
+
await client.post.deletePost({
|
156
296
|
postId: '1234567890',
|
157
297
|
});
|
158
298
|
```
|
159
299
|
|
160
|
-
###
|
300
|
+
### Getting Activity Data
|
161
301
|
|
162
302
|
```typescript
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
targets: [{ platform: 'twitter', userId: 'twitter_user_id' }], // Example target
|
169
|
-
content: {
|
170
|
-
text: 'Hello from Crosspost SDK!',
|
171
|
-
},
|
172
|
-
});
|
173
|
-
|
174
|
-
console.log(`Post created with ID: ${response.id}`);
|
175
|
-
} catch (error) {
|
176
|
-
if (error instanceof ApiError) {
|
177
|
-
// Handle authentication errors
|
178
|
-
if (error.code === ApiErrorCode.UNAUTHORIZED) {
|
179
|
-
console.error('Authentication required. Please sign in with your NEAR wallet.');
|
180
|
-
// Redirect to authentication flow or show login UI
|
181
|
-
} else {
|
182
|
-
console.error(`API Error: ${error.message}`);
|
183
|
-
console.error(`Error Code: ${error.code}`); // e.g., RATE_LIMITED
|
184
|
-
console.error(`Status: ${error.status}`); // HTTP status code
|
185
|
-
console.error(`Details:`, error.details); // Additional context
|
186
|
-
console.error(`Recoverable: ${error.recoverable}`);
|
187
|
-
}
|
188
|
-
} else if (error instanceof PlatformError) {
|
189
|
-
// Handle errors specific to a platform (e.g., Twitter API error)
|
190
|
-
console.error(`Platform Error (${error.platform}): ${error.message}`);
|
191
|
-
console.error(`Original Error:`, error.originalError);
|
192
|
-
} else {
|
193
|
-
console.error(`Unexpected error:`, error);
|
194
|
-
}
|
195
|
-
}
|
196
|
-
```
|
197
|
-
|
198
|
-
## API Reference
|
303
|
+
// Get leaderboard
|
304
|
+
const leaderboard = await client.activity.getLeaderboard({
|
305
|
+
timeframe: 'week',
|
306
|
+
limit: 10,
|
307
|
+
});
|
199
308
|
|
200
|
-
|
309
|
+
// Get account activity
|
310
|
+
const activity = await client.activity.getAccountActivity('user.near', {
|
311
|
+
timeframe: 'month',
|
312
|
+
});
|
201
313
|
|
202
|
-
|
314
|
+
// Get account posts
|
315
|
+
const posts = await client.activity.getAccountPosts('user.near', {
|
316
|
+
limit: 20,
|
317
|
+
offset: 0,
|
318
|
+
});
|
319
|
+
```
|
203
320
|
|
204
|
-
|
321
|
+
### Checking Rate Limits
|
205
322
|
|
206
323
|
```typescript
|
207
|
-
|
208
|
-
|
324
|
+
// Get all rate limits
|
325
|
+
const rateLimits = await client.system.getRateLimits();
|
209
326
|
|
210
|
-
|
327
|
+
// Get rate limit for a specific endpoint
|
328
|
+
const postRateLimit = await client.system.getEndpointRateLimit('post');
|
329
|
+
```
|
211
330
|
|
212
|
-
|
213
|
-
- `nearAuthData?: NearAuthData`: NEAR authentication data object. If not provided, the client
|
214
|
-
attempts to load from the `__crosspost_auth` cookie.
|
215
|
-
- `timeout?: number`: Request timeout in milliseconds (default: 30000).
|
216
|
-
- `retries?: number`: Number of retries for failed requests (network/5xx errors) (default: 2).
|
331
|
+
## Authentication and Security
|
217
332
|
|
218
|
-
|
333
|
+
### Authentication Strategies
|
219
334
|
|
220
|
-
|
221
|
-
in the client and stores it in the `__crosspost_auth` cookie for future use.
|
335
|
+
The SDK supports two authentication strategies:
|
222
336
|
|
223
|
-
|
337
|
+
1. **Direct Authentication**: Provide `nearAuthData` directly in the constructor.
|
338
|
+
```typescript
|
339
|
+
const client = new CrosspostClient({
|
340
|
+
nearAuthData: {
|
341
|
+
accountId: 'your-account.near',
|
342
|
+
publicKey: 'ed25519:...',
|
343
|
+
signature: '...',
|
344
|
+
message: '...',
|
345
|
+
},
|
346
|
+
});
|
347
|
+
```
|
224
348
|
|
225
|
-
-
|
226
|
-
|
349
|
+
2. **Cookie-Based Authentication**: Automatically read/write authentication data from a secure
|
350
|
+
cookie.
|
351
|
+
```typescript
|
352
|
+
// Initialize without auth data (will check for cookie)
|
353
|
+
const client = new CrosspostClient();
|
227
354
|
|
228
|
-
|
355
|
+
// Set authentication (also stores in cookie)
|
356
|
+
client.setAuthentication(nearAuthData);
|
357
|
+
```
|
229
358
|
|
230
|
-
|
359
|
+
### Cookie Security
|
231
360
|
|
232
|
-
|
233
|
-
|
234
|
-
- `getNearAuthorizationStatus(): Promise<NearAuthorizationResponse>`: Checks if the current NEAR
|
235
|
-
account is authorized.
|
236
|
-
- `loginToPlatform(platform, options?): Promise<EnhancedApiResponse<any>>`: Initiates the OAuth
|
237
|
-
login flow for a platform.
|
238
|
-
- `refreshToken(platform): Promise<EnhancedApiResponse<any>>`: Refreshes the platform token.
|
239
|
-
- `refreshProfile(platform): Promise<EnhancedApiResponse<any>>`: Refreshes the user's profile from
|
240
|
-
the platform.
|
241
|
-
- `getAuthStatus(platform): Promise<AuthStatusResponse>`: Gets the authentication status for a
|
242
|
-
specific platform.
|
243
|
-
- `revokeAuth(platform): Promise<AuthRevokeResponse>`: Revokes access for a specific platform.
|
244
|
-
- `getConnectedAccounts(): Promise<ConnectedAccountsResponse>`: Lists all platform accounts
|
245
|
-
connected to the NEAR account.
|
361
|
+
When using cookie-based authentication, the SDK stores authentication data in a secure cookie with
|
362
|
+
the following settings:
|
246
363
|
|
247
|
-
|
248
|
-
|
249
|
-
- `
|
250
|
-
-
|
251
|
-
-
|
252
|
-
- `replyToPost(request: ReplyToPostRequest): Promise<ReplyToPostResponse>`: Replies to an existing
|
253
|
-
post.
|
254
|
-
- `likePost(request: LikePostRequest): Promise<LikePostResponse>`: Likes a post.
|
255
|
-
- `unlikePost(request: UnlikePostRequest): Promise<UnlikePostResponse>`: Unlikes a post.
|
256
|
-
- `deletePost(request: DeletePostRequest): Promise<DeletePostResponse>`: Deletes one or more posts.
|
364
|
+
- **Name**: `__crosspost_auth`
|
365
|
+
- **Secure**: `true` (only sent over HTTPS)
|
366
|
+
- **SameSite**: `Lax` (sent with same-site requests and top-level navigations)
|
367
|
+
- **Path**: `/` (available across the entire domain)
|
368
|
+
- **Expires**: 30 days
|
257
369
|
|
258
370
|
### CSRF Protection
|
259
371
|
|
260
|
-
The SDK
|
261
|
-
|
262
|
-
1. The backend API sets a CSRF token in a non-HttpOnly cookie named `XSRF-TOKEN`
|
263
|
-
2. The SDK automatically reads this token and includes it in the `X-CSRF-Token` header for all
|
264
|
-
state-changing requests (non-GET)
|
265
|
-
3. The backend API validates that the token in the header matches the token in the cookie
|
372
|
+
The SDK implements CSRF protection for state-changing requests (non-GET) using the Double Submit
|
373
|
+
Cookie pattern:
|
266
374
|
|
267
|
-
|
375
|
+
1. The backend API sets a CSRF token in a non-HttpOnly cookie (`XSRF-TOKEN`)
|
376
|
+
2. The SDK reads this token and includes it in the `X-CSRF-Token` header for all state-changing
|
377
|
+
requests
|
378
|
+
3. The backend validates that the token in the header matches the token in the cookie
|
268
379
|
|
269
|
-
|
270
|
-
|
380
|
+
This protection is automatically enabled when using cookie-based authentication and requires no
|
381
|
+
additional configuration from the client side.
|
271
382
|
|
272
383
|
## License
|
273
384
|
|