CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-simple-oauth2

Node.js client for OAuth2

Pending
Overview
Eval results
Files

resource-owner-password.mddocs/

Resource Owner Password Grant

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.

Overview

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.

Core Import

const { ResourceOwnerPassword } = require('simple-oauth2');

Resource Owner Password Class

Constructor

constructor(options: OAuth2Config): ResourceOwnerPassword

Creates 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'
  }
});

Get Access Token

getToken(params: PasswordParams, httpOptions?: any): Promise<AccessToken>

Requests an access token using user credentials.

Parameters:

  • params.username (string) - User's username or email
  • params.password (string) - User's password
  • params.scope (string | string[], optional) - Application scopes to request
  • Additional parameters are automatically serialized for the token request
  • httpOptions (object, optional) - HTTP options passed to underlying request library

Returns: 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);

Create Token from Object

createToken(token: any): AccessToken

Creates an AccessToken instance from a plain token object (e.g., from storage).

Parameters:

  • token - Plain object representing an access token conforming to RFC 6750

Returns: 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);
}

Type Definitions

interface PasswordParams {
  username: string;
  password: string;
  scope?: string | string[];
  [key: string]: any;
}

Common Usage Patterns

Mobile App Authentication

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
    };
  }
}

Legacy Application Migration

// 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'
);

Desktop Application with Token Persistence

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();

Security Considerations

When using the Resource Owner Password grant:

  1. Only use in trusted applications - Never use in third-party applications
  2. Secure credential handling - Never log or store user passwords
  3. Use HTTPS always - Credentials are transmitted in requests
  4. Implement proper token storage - Use secure storage mechanisms
  5. Consider migration path - Plan to migrate to Authorization Code flow when possible
  6. Short token lifetimes - Use shorter expiration times than other flows

Install with Tessl CLI

npx tessl i tessl/npm-simple-oauth2

docs

access-token.md

authorization-code.md

client-credentials.md

index.md

resource-owner-password.md

tile.json