Skip to content

A TypeScript/JavaScript library to implement the OAuth2 AuthCode flow with PKCE - the only recommended flow for web applications

License

Notifications You must be signed in to change notification settings

Archelyst/oauth2-pkce

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OAuth2PKCE client

This library implements the OAuth Authorization Code grant (RFC 6749 § 4.1) with PKCE (RFC 7636) for single page applications in the browser.

Installation

npm install oauth2-pkce

Usage

Create Client

First you need to create the client with all the parameters necessary for OAuth. There are also three callbacks available that allow to you react on common events.

const oauthClient = new OAuth2AuthCodePkceClient({
    scopes: ['openid', 'blah'],
    authorizationUrl: AUTH_API + 'authorize/',
    tokenUrl: 'https://auth.example.com/token/',
    clientId: 'identifier-for-the-app-which-is-registered-in-the-backend',
    redirectUrl: 'https://app.example.com/return/'`,
    storeRefreshToken: false,
    // optional:
    onAccessTokenExpiry() {
        // when the access token has expired
        return oauthClient.exchangeRefreshTokenForAccessToken();
    },
    onInvalidGrant() {
        // when there is an error getting a token with a grant
        console.warn('Invalid grant! Auth code or refresh token need to be renewed.');
        // you probably want to redirect the user to the login page here
    },
    onInvalidToken() {
        // the token is invalid, e. g. because it has been removed in the backend
        console.warn('Invalid token! Refresh and access tokens need to be renewed.');
        // you probably want to redirect the user to the login page here
    }
});

The optional storeRefreshToken setting tells the client to store refresh tokens from the auth server in the browser's local storage in order to be logged in indefinitely (until calling reset()), defaults to false. This is not considered secure, so use cautiously. The refresh token is stored in memory anyway, so the users are logged in as long as they don't refresh/close the page or the access token is valid.

Authenticate

First you need to get an authorization code:

await oauthClient.requestAuthorizationCode();

This will navigate to the auth server where the user is asked to login and acknowledge the request to access the scopes. (So any code following this will never be executed.)

Afterwards, the user is redirected to the redirectUrl configured above. The redirect includes an authorization code which you need to grab and then use it to get the tokens with which further requests can be authorized:

await oauthClient.receiveCode();
const tokens = await oauthClient.getTokens();

This library supports access tokens, refresh tokens, and id tokens.

Use the tokens

Now you can use the token in order to make requests. There are some mechanisms to help you with that.

Automatically Get New Access Token

Instead of checking the validity of an access token all the time, the app might just assume it is valid. When using the fetch() API there is a way to automatically get a new access token in case the backend indicates that the access token is no longer valid. Just wrap your fetch function like so:

window.fetch = oauthClient.makeRetryFetchFunction(window.fetch);

This will wrap the original fetch function and transparently make it use the refresh token to get a new access token and retry.

Use Interceptors

Many frameworks or libraries offer the concept of request/response interceptors. Those are simple functions which may alter requests before they are actually sent to the backend as well as the response when it comes back. oauth2-pkce offers both for putting the access token in the request and handling errors in the response:

// depending on your framework something like
httpClient.registerRequestInterceptor(oauthClient.requestInterceptor);
httpClient.registerResponseInterceptor(oauthClient.responseInterceptor);
// or manually
async function handleRequest(request) {
    request = await oauthClient.requestInterceptor(request);
    ...
    return request;
}
async function handleResponse(response) {
    response = await oauthClient.responseInterceptor(response);
    ...
    return response;
}

There is also a shortcut in case your setup doesn't feature interceptors:

window.fetch = oauthClient.decorateFetchWithInterceptors(window.fetch);

Check Authorization State

With oauthClient.isAuthorized() you can check whether the user has an access token. This does not actually send a request to the backend. It allows you to decide if the user has to be redirected to login.

Logout

When a user logs out, all tokens need to be dismissed:

await oauthClient.reset()

This doesn't redirect or do anything else to indicate to the user that they are no longer logged in. That's the responsibility of the app, e. g. to redirect to the login page.

Additional Parameters

While most OAuth servers are just fine with the standard parameters, you have several options to pass additional parameters to the authentication server if required.

Static Configuration Parameters

  • Add a map of parameters as extraAuthorizationParams to the configuration. They will be sent to the server when a new auth code is requested.
  • The same works for the request that fetches a refresh token: add parameters to the configuration as extraRefreshParams.

These parameters are defined once and used through the lifetime of the client.

Dynamic Parameters

Several functions allow to pass oneTimeParams in order to send custom parameters to the server that might change in the course of the program's lifetime: requestAuthorizationCode(), getTokens(), and exchangeAuthCodeForAccessToken().

Precedence

By providing additional parameters, you can also overwrite the standard ones. Also, the dynamicly provided parameters supersede the ones provided in the config:

dynamic parameters overwrite > configuration parameters overwrite > standard parameters

Storage

OAuth2PKCE holds some state like the current access token. It needs to be persisted in a way that survives reloads because of the redirects during authentication. By default local storage is used, but you can provide your own implementation, e.g. if you're creating an app and you want to use platform specific storage:

import Storage from 'oauth2-pkce';

class MyOwnStorage implements Storage {
    saveState(serializedState: string) { ... // store somewhere }
    loadState() { return ...; // return the stored string }
}

const myOwnStorage = new MyOwnStorage();

const oauthClient = new OAuth2AuthCodePkceClient(config, myOwnStorage);

Both methods can by async / return a promise.

Changelog

2.1.1

  • Fixed optional oneTimeParams

2.1.0

  • Added oneTimeParams to getTokens() and exchangeAuthCodeForAccessToken()

2.0.0

  • Feature: Made the state storage pluggable
  • Breaking change: receiveCode() and reset() are async now

Acknowledgements

This library is basically a rewrite of https://github.com/BitySA/oauth2-auth-code-pkce/ with the goal of having more maintanable and customizable code.

Development was funded by http://comsulting.de

About

A TypeScript/JavaScript library to implement the OAuth2 AuthCode flow with PKCE - the only recommended flow for web applications

Resources

License

Stars

Watchers

Forks

Packages

No packages published