Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

@urql/exchange-auth

urql-graphql447.1kMIT2.2.1TypeScript support: included

An exchange for managing authentication and token refresh in urql

urql, exchange, auth, authentication, graphql, exchanges

readme

@urql/exchange-auth

An exchange for managing authentication in urql

@urql/exchange-auth is an exchange for the urql GraphQL client which helps handle auth headers and token refresh

Quick Start Guide

First install @urql/exchange-auth alongside urql:

yarn add @urql/exchange-auth
# or
npm install --save @urql/exchange-auth

You'll then need to add the authExchange, that this package exposes to your urql Client

import { createClient, cacheExchange, fetchExchange } from 'urql';
import { makeOperation } from '@urql/core';
import { authExchange } from '@urql/exchange-auth';

const client = createClient({
  url: 'http://localhost:1234/graphql',
  exchanges: [
    cacheExchange,
    authExchange(async utils => {
      // called on initial launch,
      // fetch the auth state from storage (local storage, async storage etc)
      let token = localStorage.getItem('token');
      let refreshToken = localStorage.getItem('refreshToken');

      return {
        addAuthToOperation(operation) {
          if (token) {
            return utils.appendHeaders(operation, {
              Authorization: `Bearer ${token}`,
            });
          }
          return operation;
        },
        willAuthError(_operation) {
          // e.g. check for expiration, existence of auth etc
          return !token;
        },
        didAuthError(error, _operation) {
          // check if the error was an auth error
          // this can be implemented in various ways, e.g. 401 or a special error code
          return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
        },
        async refreshAuth() {
          // called when auth error has occurred
          // we should refresh the token with a GraphQL mutation or a fetch call,
          // depending on what the API supports
          const result = await mutate(refreshMutation, {
            token: authState?.refreshToken,
          });

          if (result.data?.refreshLogin) {
            // save the new tokens in storage for next restart
            token = result.data.refreshLogin.token;
            refreshToken = result.data.refreshLogin.refreshToken;
            localStorage.setItem('token', token);
            localStorage.setItem('refreshToken', refreshToken);
          } else {
            // otherwise, if refresh fails, log clear storage and log out
            localStorage.clear();
            logout();
          }
        },
      };
    }),
    fetchExchange,
  ],
});

Handling Errors via the errorExchange

Handling the logout logic in refreshAuth is the easiest way to get started, but it means the errors will always get swallowed by the authExchange. If you want to handle errors globally, this can be done using the mapExchange:

import { mapExchange } from 'urql';

// this needs to be placed ABOVE the authExchange in the exchanges array, otherwise the auth error
// will show up hear before the auth exchange has had the chance to handle it
mapExchange({
  onError(error) {
    // we only get an auth error here when the auth exchange had attempted to refresh auth and
    // getting an auth error again for the second time
    const isAuthError = error.graphQLErrors.some(
      e => e.extensions?.code === 'FORBIDDEN',
    );
    if (isAuthError) {
      // clear storage, log the user out etc
    }
  }
}),

changelog

Changelog

2.2.1

Patch Changes

2.2.0

Minor Changes

  • Mark @urql/core as a peer dependency as well as a regular dependency Submitted by @kitten (See #3579)

2.1.6

Patch Changes

  • authExchange() will now block and pass on errors if the initialization function passed to it fails, and will retry indefinitely. It’ll also output a warning for these cases, as the initialization function (i.e. authExchange(async (utils) => { /*...*/ })) is not expected to reject/throw Submitted by @kitten (See #3343)

2.1.5

Patch Changes

  • Handle refreshAuth rejections and pass the resulting error on to OperationResults on the authentication queue Submitted by @kitten (See #3307)

2.1.4

Patch Changes

  • ⚠️ Fix regression that caused teardowns to be ignored by an authExchange’s retry queue Submitted by @kitten (See #3235)

2.1.3

Patch Changes

  • Update build process to generate correct source maps Submitted by @kitten (See #3201)

2.1.2

Patch Changes

  • Publish with npm provenance Submitted by @kitten (See #3180)

2.1.1

Patch Changes

  • ⚠️ Fix operations created by utilities.mutate() erroneously being retried and sent again like a regular operation Submitted by @JoviDeCroock (See #3164)

2.1.0

Minor Changes

  • Update exchanges to drop redundant share calls, since @urql/core’s composeExchanges utility now automatically does so for us Submitted by @kitten (See #3082)

Patch Changes

2.0.0

Major Changes

  • Implement new authExchange API, which removes the need for an authState (i.e. an internal authentication state) and removes getAuth, replacing it with a separate refreshAuth flow. The new API requires you to now pass an initializer function. This function receives a utils object with utils.mutate and utils.appendHeaders utility methods. It must return the configuration object, wrapped in a promise, and this configuration is similar to what we had before, if you're migrating to this. Its refreshAuth method is now only called after authentication errors occur and not on initialization. Instead, it's now recommended that you write your initialization logic in-line.
    authExchange(async utils => {
      let token = localStorage.getItem('token');
      let refreshToken = localStorage.getItem('refreshToken');
      return {
        addAuthToOperation(operation) {
          return utils.appendHeaders(operation, {
            Authorization: `Bearer ${token}`,
          });
        },
        didAuthError(error) {
          return error.graphQLErrors.some(
            e => e.extensions?.code === 'FORBIDDEN'
          );
        },
        async refreshAuth() {
          const result = await utils.mutate(REFRESH, { token });
          if (result.data?.refreshLogin) {
            token = result.data.refreshLogin.token;
            refreshToken = result.data.refreshLogin.refreshToken;
            localStorage.setItem('token', token);
            localStorage.setItem('refreshToken', refreshToken);
          }
        },
      };
    });
    Submitted by @kitten (See #3012)

Patch Changes

  • ⚠️ Fix willAuthError not being called for operations that are waiting on the authentication state to update. This can actually lead to a common issue where operations that came in during the authentication initialization (on startup) will never have willAuthError called on them. This can cause an easy mistake where the initial authentication state is never checked to be valid Submitted by @kitten (See #3017)
  • Updated dependencies (See #3007, #2962, #3007, #3015, and #3022)

1.0.0

Major Changes

  • Goodbye IE11! 👋 This major release removes support for IE11. All code that is shipped will be transpiled much less and will not be ES5-compatible anymore, by @kitten (See #2504)
  • Upgrade to Wonka v6 (wonka@^6.0.0), which has no breaking changes but is built to target ES2015 and comes with other minor improvements. The library has fully been migrated to TypeScript which will hopefully help with making contributions easier!, by @kitten (See #2504)

Minor Changes

  • Remove the babel-plugin-modular-graphql helper, this because the graphql package hasn't converted to ESM yet which gives issues in node environments, by @JoviDeCroock (See #2551)

Patch Changes

0.1.7

Patch Changes

  • Extend peer dependency range of graphql to include ^16.0.0. As always when upgrading across many packages of urql, especially including @urql/core we recommend you to deduplicate dependencies after upgrading, using npm dedupe or npx yarn-deduplicate, by @kitten (See #2133)
  • Updated dependencies (See #2133)

0.1.6

Patch Changes

0.1.5

Patch Changes

0.1.4

Patch Changes

  • Allow mutate to infer the result's type when a TypedDocumentNode is passed via the usual generics, like client.mutation for instance, by @younesmln (See #1796)

0.1.3

Patch Changes

0.1.2

Patch Changes

  • Deprecate the Operation.operationName property in favor of Operation.kind. This name was previously confusing as operationName was effectively referring to two different things. You can safely upgrade to this new version, however to mute all deprecation warnings you will have to upgrade all urql packages you use. If you have custom exchanges that spread operations, please use the new makeOperation helper function instead, by @bkonkle (See #1045)
  • Updated dependencies (See #1094 and #1045)

0.1.1

Patch Changes

  • ⚠️ Fix an operation that triggers willAuthError with a truthy return value being sent off twice, by @kitten (See #1075)

v0.1.0

Initial Release