0
# Resource Owner Password Grant
1
2
The Resource Owner Password Credentials Grant allows applications to directly use a user's credentials (username and password) to obtain an access token. This flow is only recommended for trusted applications where other flows are not viable.
3
4
## Overview
5
6
This grant type implements the OAuth 2.0 Resource Owner Password Credentials flow, which directly exchanges user credentials for an access token. **This flow is deprecated in OAuth 2.0 Security Best Current Practice** due to security concerns, but is still supported for legacy applications.
7
8
**Warning:** Only use this grant type for highly trusted applications (like official mobile apps) where other OAuth flows are not possible.
9
10
## Core Import
11
12
```javascript
13
const { ResourceOwnerPassword } = require('simple-oauth2');
14
```
15
16
## Resource Owner Password Class
17
18
### Constructor
19
20
```typescript { .api }
21
constructor(options: OAuth2Config): ResourceOwnerPassword
22
```
23
24
Creates a new ResourceOwnerPassword instance with OAuth 2.0 configuration validation.
25
26
**Parameters:**
27
- `options` - OAuth 2.0 configuration object (see main documentation)
28
29
**Example:**
30
```javascript
31
const client = new ResourceOwnerPassword({
32
client: {
33
id: 'your-client-id',
34
secret: 'your-client-secret'
35
},
36
auth: {
37
tokenHost: 'https://oauth-provider.com',
38
tokenPath: '/oauth/token'
39
}
40
});
41
```
42
43
### Get Access Token
44
45
```typescript { .api }
46
getToken(params: PasswordParams, httpOptions?: any): Promise<AccessToken>
47
```
48
49
Requests an access token using user credentials.
50
51
**Parameters:**
52
- `params.username` (string) - User's username or email
53
- `params.password` (string) - User's password
54
- `params.scope` (string | string[], optional) - Application scopes to request
55
- Additional parameters are automatically serialized for the token request
56
- `httpOptions` (object, optional) - HTTP options passed to underlying request library
57
58
**Returns:** Promise resolving to AccessToken instance
59
60
**Example:**
61
```javascript
62
// Basic authentication
63
const accessToken = await client.getToken({
64
username: 'user@example.com',
65
password: 'user-password'
66
});
67
68
// Authentication with specific scopes
69
const accessToken = await client.getToken({
70
username: 'user@example.com',
71
password: 'user-password',
72
scope: ['read', 'write', 'profile']
73
});
74
75
// Authentication with custom HTTP options
76
const accessToken = await client.getToken({
77
username: 'user@example.com',
78
password: 'user-password',
79
scope: 'api:access'
80
}, {
81
timeout: 15000,
82
headers: {
83
'User-Agent': 'TrustedApp/2.0'
84
}
85
});
86
87
console.log('Access token:', accessToken.token.access_token);
88
console.log('Refresh token:', accessToken.token.refresh_token);
89
```
90
91
### Create Token from Object
92
93
```typescript { .api }
94
createToken(token: any): AccessToken
95
```
96
97
Creates an AccessToken instance from a plain token object (e.g., from storage).
98
99
**Parameters:**
100
- `token` - Plain object representing an access token conforming to RFC 6750
101
102
**Returns:** AccessToken instance with full token management capabilities
103
104
**Example:**
105
```javascript
106
// Restore token from secure storage
107
const storedToken = await secureStorage.getToken(userId);
108
const accessToken = client.createToken(storedToken);
109
110
// Refresh token if expired
111
if (accessToken.expired()) {
112
const refreshedToken = await accessToken.refresh();
113
await secureStorage.saveToken(userId, refreshedToken.token);
114
}
115
```
116
117
## Type Definitions
118
119
```typescript { .api }
120
interface PasswordParams {
121
username: string;
122
password: string;
123
scope?: string | string[];
124
[key: string]: any;
125
}
126
```
127
128
## Common Usage Patterns
129
130
### Mobile App Authentication
131
132
```javascript
133
const { ResourceOwnerPassword } = require('simple-oauth2');
134
135
// Only use in trusted mobile applications
136
const client = new ResourceOwnerPassword({
137
client: {
138
id: process.env.MOBILE_APP_CLIENT_ID,
139
secret: process.env.MOBILE_APP_CLIENT_SECRET
140
},
141
auth: {
142
tokenHost: 'https://api.company.com',
143
tokenPath: '/oauth/token'
144
}
145
});
146
147
// Handle user login
148
async function authenticateUser(username, password) {
149
try {
150
const accessToken = await client.getToken({
151
username: username,
152
password: password,
153
scope: 'profile data:read data:write'
154
});
155
156
// Store token securely
157
await secureStorage.setItem('auth_token', JSON.stringify(accessToken.token));
158
159
return {
160
success: true,
161
token: accessToken.token
162
};
163
} catch (error) {
164
console.error('Authentication failed:', error.message);
165
return {
166
success: false,
167
error: error.message
168
};
169
}
170
}
171
```
172
173
### Legacy Application Migration
174
175
```javascript
176
// For migrating legacy applications to OAuth 2.0
177
class LegacyAuthAdapter {
178
constructor(oauthConfig) {
179
this.oauth = new ResourceOwnerPassword(oauthConfig);
180
this.tokenCache = new Map();
181
}
182
183
async login(username, password) {
184
try {
185
const accessToken = await this.oauth.getToken({
186
username,
187
password,
188
scope: 'legacy:api'
189
});
190
191
// Cache token for this user session
192
this.tokenCache.set(username, accessToken);
193
194
return accessToken.token;
195
} catch (error) {
196
throw new Error(`Login failed: ${error.message}`);
197
}
198
}
199
200
async getValidToken(username) {
201
const cachedToken = this.tokenCache.get(username);
202
203
if (!cachedToken) {
204
throw new Error('User not authenticated');
205
}
206
207
// Check if token needs refresh
208
if (cachedToken.expired(300)) { // 5 minute buffer
209
const refreshedToken = await cachedToken.refresh();
210
this.tokenCache.set(username, refreshedToken);
211
return refreshedToken.token;
212
}
213
214
return cachedToken.token;
215
}
216
217
async makeAuthenticatedRequest(username, url, options = {}) {
218
const token = await this.getValidToken(username);
219
220
return fetch(url, {
221
...options,
222
headers: {
223
...options.headers,
224
'Authorization': `Bearer ${token.access_token}`
225
}
226
});
227
}
228
}
229
230
// Usage
231
const authAdapter = new LegacyAuthAdapter({
232
client: {
233
id: 'legacy-app-id',
234
secret: process.env.LEGACY_APP_SECRET
235
},
236
auth: {
237
tokenHost: 'https://auth.company.com'
238
}
239
});
240
241
// User login
242
await authAdapter.login('user@company.com', 'password');
243
244
// Make authenticated API calls
245
const response = await authAdapter.makeAuthenticatedRequest(
246
'user@company.com',
247
'https://api.company.com/user/profile'
248
);
249
```
250
251
### Desktop Application with Token Persistence
252
253
```javascript
254
const path = require('path');
255
const fs = require('fs').promises;
256
const { ResourceOwnerPassword } = require('simple-oauth2');
257
258
class DesktopAuthClient {
259
constructor(clientConfig) {
260
this.oauth = new ResourceOwnerPassword(clientConfig);
261
this.tokenPath = path.join(process.env.HOME, '.myapp', 'token.json');
262
}
263
264
async authenticate(username, password) {
265
try {
266
const accessToken = await this.oauth.getToken({
267
username,
268
password,
269
scope: 'desktop:full'
270
});
271
272
// Save token to file
273
await this.saveToken(accessToken.token);
274
return accessToken;
275
} catch (error) {
276
throw new Error(`Authentication failed: ${error.message}`);
277
}
278
}
279
280
async getStoredToken() {
281
try {
282
const tokenData = await fs.readFile(this.tokenPath, 'utf8');
283
const token = JSON.parse(tokenData);
284
return this.oauth.createToken(token);
285
} catch (error) {
286
return null; // No stored token
287
}
288
}
289
290
async saveToken(token) {
291
const dir = path.dirname(this.tokenPath);
292
await fs.mkdir(dir, { recursive: true });
293
await fs.writeFile(this.tokenPath, JSON.stringify(token, null, 2));
294
}
295
296
async getValidToken() {
297
const storedToken = await this.getStoredToken();
298
299
if (!storedToken) {
300
throw new Error('No authentication token found. Please login first.');
301
}
302
303
if (storedToken.expired()) {
304
const refreshedToken = await storedToken.refresh();
305
await this.saveToken(refreshedToken.token);
306
return refreshedToken;
307
}
308
309
return storedToken;
310
}
311
}
312
313
// Usage
314
const authClient = new DesktopAuthClient({
315
client: {
316
id: 'desktop-app-id',
317
secret: process.env.DESKTOP_APP_SECRET
318
},
319
auth: {
320
tokenHost: 'https://auth.company.com'
321
}
322
});
323
324
// Initial authentication
325
await authClient.authenticate('user@company.com', 'password');
326
327
// Later, get valid token (will refresh if needed)
328
const token = await authClient.getValidToken();
329
```
330
331
## Security Considerations
332
333
When using the Resource Owner Password grant:
334
335
1. **Only use in trusted applications** - Never use in third-party applications
336
2. **Secure credential handling** - Never log or store user passwords
337
3. **Use HTTPS always** - Credentials are transmitted in requests
338
4. **Implement proper token storage** - Use secure storage mechanisms
339
5. **Consider migration path** - Plan to migrate to Authorization Code flow when possible
340
6. **Short token lifetimes** - Use shorter expiration times than other flows