0
# State Management
1
2
CSRF protection through encrypted state parameters that securely transfer installation options between OAuth endpoints while preventing replay attacks.
3
4
## Capabilities
5
6
### StateStore Interface
7
8
Interface for managing OAuth state parameters with encryption and verification.
9
10
```typescript { .api }
11
/**
12
* Interface for managing OAuth state parameters
13
*/
14
interface StateStore {
15
/**
16
* Generate encrypted state parameter for OAuth URL
17
* @param installOptions - OAuth options to encode in state
18
* @param now - Current timestamp for expiration tracking
19
* @returns Encrypted state string for OAuth URL
20
*/
21
generateStateParam(
22
installOptions: InstallURLOptions,
23
now: Date
24
): Promise<string>;
25
26
/**
27
* Verify and decode state parameter from OAuth callback
28
* @param now - Current timestamp for expiration validation
29
* @param state - Encrypted state string from OAuth callback
30
* @returns Decoded installation options from state
31
* @throws InvalidStateError if state is invalid or expired
32
*/
33
verifyStateParam(now: Date, state: string): Promise<InstallURLOptions>;
34
}
35
```
36
37
### State Data Structure
38
39
Data structure represented by the state parameter.
40
41
```typescript { .api }
42
/**
43
* Data structure encoded in OAuth state parameter
44
*/
45
interface StateObj {
46
/** Timestamp when state was generated */
47
now: Date;
48
/** Installation options passed through OAuth flow */
49
installOptions: InstallURLOptions;
50
/** Optional random value for additional entropy */
51
random?: string | number;
52
}
53
```
54
55
### Built-in State Stores
56
57
Ready-to-use implementations of the StateStore interface.
58
59
#### ClearStateStore
60
61
```typescript { .api }
62
/**
63
* JWT-based state store with encryption using a secret key
64
*/
65
class ClearStateStore implements StateStore {
66
/**
67
* @param stateSecret - Secret key for JWT signing and encryption
68
* @param expirationSeconds - State expiration time in seconds (default: 600)
69
*/
70
constructor(stateSecret: string, expirationSeconds?: number);
71
72
async generateStateParam(
73
installOptions: InstallURLOptions,
74
now: Date
75
): Promise<string>;
76
77
async verifyStateParam(now: Date, state: string): Promise<InstallURLOptions>;
78
}
79
```
80
81
#### FileStateStore
82
83
```typescript { .api }
84
/**
85
* File-based state store for development and simple deployments
86
*/
87
class FileStateStore implements StateStore {
88
/**
89
* @param args - Configuration options for file storage
90
*/
91
constructor(args: FileStateStoreArgs);
92
93
async generateStateParam(
94
installOptions: InstallURLOptions,
95
now: Date
96
): Promise<string>;
97
98
async verifyStateParam(now: Date, state: string): Promise<InstallURLOptions>;
99
}
100
101
/**
102
* Configuration options for FileStateStore
103
*/
104
interface FileStateStoreArgs {
105
/** Directory to store state files (default: homedir/.bolt-js-oauth-states) */
106
baseDir?: string;
107
/** State expiration time in seconds (default: 600) */
108
stateExpirationSeconds?: number;
109
/** Logger instance for debugging */
110
logger?: Logger;
111
}
112
```
113
114
### InstallPathOptions
115
116
Options for configuring install path behavior with custom request handling.
117
118
```typescript { .api }
119
interface InstallPathOptions {
120
/**
121
* Custom handler called before redirecting to Slack OAuth URL
122
* Return false to skip OAuth redirect and handle response manually
123
* @param request - Incoming HTTP request
124
* @param response - HTTP response for custom handling
125
* @param options - OAuth options for this installation
126
* @returns true to continue with OAuth, false to skip
127
*/
128
beforeRedirection?: (
129
request: IncomingMessage,
130
response: ServerResponse,
131
options?: InstallURLOptions
132
) => Promise<boolean>;
133
}
134
```
135
136
**Usage Examples:**
137
138
```typescript
139
import {
140
InstallProvider,
141
ClearStateStore,
142
FileStateStore,
143
StateStore,
144
StateObj
145
} from "@slack/oauth";
146
147
// Using default ClearStateStore (recommended)
148
const installer = new InstallProvider({
149
clientId: process.env.SLACK_CLIENT_ID!,
150
clientSecret: process.env.SLACK_CLIENT_SECRET!,
151
stateSecret: "my-secret-key", // Required for ClearStateStore
152
});
153
154
// Using FileStateStore
155
const fileStateStore = new FileStateStore({
156
baseDir: "./oauth-states",
157
expirationSeconds: 300, // 5 minutes
158
});
159
160
const installer2 = new InstallProvider({
161
clientId: process.env.SLACK_CLIENT_ID!,
162
clientSecret: process.env.SLACK_CLIENT_SECRET!,
163
stateStore: fileStateStore,
164
});
165
166
// Custom state store implementation
167
class RedisStateStore implements StateStore {
168
private redis: RedisClient;
169
private expirationSeconds: number;
170
171
constructor(redis: RedisClient, expirationSeconds = 600) {
172
this.redis = redis;
173
this.expirationSeconds = expirationSeconds;
174
}
175
176
async generateStateParam(installOptions: InstallURLOptions, now: Date): Promise<string> {
177
const stateId = `state_${Date.now()}_${Math.random()}`;
178
const stateObj: StateObj = {
179
now,
180
installOptions,
181
random: Math.random(),
182
};
183
184
// Store in Redis with expiration
185
await this.redis.setex(
186
stateId,
187
this.expirationSeconds,
188
JSON.stringify(stateObj)
189
);
190
191
return stateId;
192
}
193
194
async verifyStateParam(now: Date, state: string): Promise<InstallURLOptions> {
195
const stateData = await this.redis.get(state);
196
if (!stateData) {
197
throw new InvalidStateError("State not found or expired");
198
}
199
200
const stateObj: StateObj = JSON.parse(stateData);
201
202
// Check expiration
203
const ageMs = now.getTime() - new Date(stateObj.now).getTime();
204
if (ageMs > this.expirationSeconds * 1000) {
205
throw new InvalidStateError("State has expired");
206
}
207
208
// Clean up used state
209
await this.redis.del(state);
210
211
return stateObj.installOptions;
212
}
213
}
214
215
// Manual state operations
216
const stateStore = new ClearStateStore("secret-key");
217
const installOptions = {
218
scopes: ["chat:write"],
219
metadata: "custom-data",
220
};
221
222
// Generate state for OAuth URL
223
const state = await stateStore.generateStateParam(installOptions, new Date());
224
console.log("Generated state:", state);
225
226
// Later, verify state from callback
227
try {
228
const decodedOptions = await stateStore.verifyStateParam(new Date(), state);
229
console.log("Decoded options:", decodedOptions);
230
} catch (error) {
231
console.error("State verification failed:", error.message);
232
}
233
234
// Disable state verification (not recommended except for enterprise admin installs)
235
const noStateInstaller = new InstallProvider({
236
clientId: process.env.SLACK_CLIENT_ID!,
237
clientSecret: process.env.SLACK_CLIENT_SECRET!,
238
stateVerification: false, // Disables CSRF protection
239
});
240
```