Node.js client for OAuth2
—
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.
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.
Warning: Only use this grant type for highly trusted applications (like official mobile apps) where other OAuth flows are not possible.
const { ResourceOwnerPassword } = require('simple-oauth2');constructor(options: OAuth2Config): ResourceOwnerPasswordCreates a new ResourceOwnerPassword instance with OAuth 2.0 configuration validation.
Parameters:
options - OAuth 2.0 configuration object (see main documentation)Example:
const client = new ResourceOwnerPassword({
client: {
id: 'your-client-id',
secret: 'your-client-secret'
},
auth: {
tokenHost: 'https://oauth-provider.com',
tokenPath: '/oauth/token'
}
});getToken(params: PasswordParams, httpOptions?: any): Promise<AccessToken>Requests an access token using user credentials.
Parameters:
params.username (string) - User's username or emailparams.password (string) - User's passwordparams.scope (string | string[], optional) - Application scopes to requesthttpOptions (object, optional) - HTTP options passed to underlying request libraryReturns: Promise resolving to AccessToken instance
Example:
// Basic authentication
const accessToken = await client.getToken({
username: 'user@example.com',
password: 'user-password'
});
// Authentication with specific scopes
const accessToken = await client.getToken({
username: 'user@example.com',
password: 'user-password',
scope: ['read', 'write', 'profile']
});
// Authentication with custom HTTP options
const accessToken = await client.getToken({
username: 'user@example.com',
password: 'user-password',
scope: 'api:access'
}, {
timeout: 15000,
headers: {
'User-Agent': 'TrustedApp/2.0'
}
});
console.log('Access token:', accessToken.token.access_token);
console.log('Refresh token:', accessToken.token.refresh_token);createToken(token: any): AccessTokenCreates an AccessToken instance from a plain token object (e.g., from storage).
Parameters:
token - Plain object representing an access token conforming to RFC 6750Returns: AccessToken instance with full token management capabilities
Example:
// Restore token from secure storage
const storedToken = await secureStorage.getToken(userId);
const accessToken = client.createToken(storedToken);
// Refresh token if expired
if (accessToken.expired()) {
const refreshedToken = await accessToken.refresh();
await secureStorage.saveToken(userId, refreshedToken.token);
}interface PasswordParams {
username: string;
password: string;
scope?: string | string[];
[key: string]: any;
}const { ResourceOwnerPassword } = require('simple-oauth2');
// Only use in trusted mobile applications
const client = new ResourceOwnerPassword({
client: {
id: process.env.MOBILE_APP_CLIENT_ID,
secret: process.env.MOBILE_APP_CLIENT_SECRET
},
auth: {
tokenHost: 'https://api.company.com',
tokenPath: '/oauth/token'
}
});
// Handle user login
async function authenticateUser(username, password) {
try {
const accessToken = await client.getToken({
username: username,
password: password,
scope: 'profile data:read data:write'
});
// Store token securely
await secureStorage.setItem('auth_token', JSON.stringify(accessToken.token));
return {
success: true,
token: accessToken.token
};
} catch (error) {
console.error('Authentication failed:', error.message);
return {
success: false,
error: error.message
};
}
}// For migrating legacy applications to OAuth 2.0
class LegacyAuthAdapter {
constructor(oauthConfig) {
this.oauth = new ResourceOwnerPassword(oauthConfig);
this.tokenCache = new Map();
}
async login(username, password) {
try {
const accessToken = await this.oauth.getToken({
username,
password,
scope: 'legacy:api'
});
// Cache token for this user session
this.tokenCache.set(username, accessToken);
return accessToken.token;
} catch (error) {
throw new Error(`Login failed: ${error.message}`);
}
}
async getValidToken(username) {
const cachedToken = this.tokenCache.get(username);
if (!cachedToken) {
throw new Error('User not authenticated');
}
// Check if token needs refresh
if (cachedToken.expired(300)) { // 5 minute buffer
const refreshedToken = await cachedToken.refresh();
this.tokenCache.set(username, refreshedToken);
return refreshedToken.token;
}
return cachedToken.token;
}
async makeAuthenticatedRequest(username, url, options = {}) {
const token = await this.getValidToken(username);
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token.access_token}`
}
});
}
}
// Usage
const authAdapter = new LegacyAuthAdapter({
client: {
id: 'legacy-app-id',
secret: process.env.LEGACY_APP_SECRET
},
auth: {
tokenHost: 'https://auth.company.com'
}
});
// User login
await authAdapter.login('user@company.com', 'password');
// Make authenticated API calls
const response = await authAdapter.makeAuthenticatedRequest(
'user@company.com',
'https://api.company.com/user/profile'
);const path = require('path');
const fs = require('fs').promises;
const { ResourceOwnerPassword } = require('simple-oauth2');
class DesktopAuthClient {
constructor(clientConfig) {
this.oauth = new ResourceOwnerPassword(clientConfig);
this.tokenPath = path.join(process.env.HOME, '.myapp', 'token.json');
}
async authenticate(username, password) {
try {
const accessToken = await this.oauth.getToken({
username,
password,
scope: 'desktop:full'
});
// Save token to file
await this.saveToken(accessToken.token);
return accessToken;
} catch (error) {
throw new Error(`Authentication failed: ${error.message}`);
}
}
async getStoredToken() {
try {
const tokenData = await fs.readFile(this.tokenPath, 'utf8');
const token = JSON.parse(tokenData);
return this.oauth.createToken(token);
} catch (error) {
return null; // No stored token
}
}
async saveToken(token) {
const dir = path.dirname(this.tokenPath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(this.tokenPath, JSON.stringify(token, null, 2));
}
async getValidToken() {
const storedToken = await this.getStoredToken();
if (!storedToken) {
throw new Error('No authentication token found. Please login first.');
}
if (storedToken.expired()) {
const refreshedToken = await storedToken.refresh();
await this.saveToken(refreshedToken.token);
return refreshedToken;
}
return storedToken;
}
}
// Usage
const authClient = new DesktopAuthClient({
client: {
id: 'desktop-app-id',
secret: process.env.DESKTOP_APP_SECRET
},
auth: {
tokenHost: 'https://auth.company.com'
}
});
// Initial authentication
await authClient.authenticate('user@company.com', 'password');
// Later, get valid token (will refresh if needed)
const token = await authClient.getValidToken();When using the Resource Owner Password grant:
Install with Tessl CLI
npx tessl i tessl/npm-simple-oauth2