js-oauth2

OAuth2 authentication module written in vanilla ES6

Usage no npm install needed!

<script type="module">
  import jsOauth2 from 'https://cdn.skypack.dev/js-oauth2';
</script>

README

js-oauth2

npm version License Dependency Status devDependency Status npm coverage report

This library is a port of angular-oauth2 to vanilla JS and fetch. Currently, this library only uses the password credential grant, i.e, using a combination (username, password), we'll request an access token (using grant_type=password) wich, in case of success, will return a response such as:

{
  "access_token": "foobar",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "foobiz"
}

Internally we'll automatically store it as a cookie and it will be used in every request adding an Authorization header: Authorization: 'Bearer foobar'.

Instalation

choose your preferred method:

  • npm: npm install --save js-oauth2
  • yarn: yarn add js-oauth2

Usage

initialize library:

import { AuthenticationService, AbstractKeychain, httpRequestInterceptor } from 'js-oauth2'
import cookie from 'js-cookie'

class Keychain extends AbstractKeychain {
  /**
   * @param {Token} value
   * @returns {Promise<void>}
   */
  setToken (value) {
    return new Promise((resolve) => {
      resolve(cookie.set('token', value))
    })
  }

  /**
   * @returns {Promise<Token>}
   */
  getToken () {
    return new Promise((resolve) => {
      resolve(cookie.getJSON('token') || {})
    })
  }

  /**
   * @returns {Promise<void>}
   */
  removeToken () {
    return new Promise((resolve) => {
      resolve(cookie.remove('token'))
    })
  }
}

const oauth = new AuthenticationService({
  baseUrl: '/api',
  clientId: 'b921b25ebe3ee70c6b1',
  clientSecret: '8f4d45d9a363d922b2eb7',
  keychain: new Keychain()
})

httpRequestInterceptor(oauth) // only if you need http request interception

API

Check authentication status:

/**
 * Verifies if the `user` is authenticated or not based on the `token`
 * cookie.
 * @return {Promise<boolean>}
 */
oauth.isAuthenticated()

Get an access token:

/**
 * Retrieves the `access_token` and stores the `response.data` on cookies
 * using the `OAuthToken`.
 * @param {object} user - Object with `username` and `password` properties.
 * @param {object} config - Optional configuration object sent to `POST`.
 * @return {Promise} A response promise.
 */

oauth.getAccessToken(user, options)

Refresh access token:

/**
 * Retrieves the `refresh_token` and stores the `response.data` on cookies
 * using the `OAuthToken`.
 * @return {Promise} A response promise.
 */

oauth.getRefreshToken()

Revoke access token:

/**
 * Revokes the `token` and removes the stored `token` from cookies
 * using the `OAuthToken`.
 * @return {Promise} A response promise.
 */

oauth.revokeToken()

Catch OAuth errors and do something with them (optional):

/**
 * @param {Response} response
 */

async function onError (response)  {
  const data = await response.clone().json()
  if (data.error === 'invalid_grant') {
    return
  }
  // Refresh token when a `invalid_token` error occurs.
  if (data.error === 'invalid_token') {
    return oauth.getRefreshToken()
  }
  // Redirect to `/login` with the `error_reason`.
  return window.location(`/login?error_reason=${data.error}`)
}

auth.onError(onError)

NOTE: An event oauth:error will be sent everytime a onError is emitted:

  • { status: 400, data: { error: 'invalid_request' } }
  • { status: 400, data: { error: 'invalid_grant' } }
  • { status: 401, data: { error: 'invalid_token' } }
  • { status: 401, headers: { 'www-authenticate': 'Bearer realm="example"' } }

References to create project