@urql/exchange-auth
Version:
An exchange for managing authentication and token refresh in urql
157 lines (154 loc) • 7.71 kB
TypeScript
import { AnyVariables, DocumentInput, OperationContext, OperationResult, Operation, CombinedError, Exchange } from '@urql/core';
/** Utilities to use while refreshing authentication tokens. */
interface AuthUtilities {
/** Sends a mutation to your GraphQL API, bypassing earlier exchanges and authentication.
*
* @param query - a GraphQL document containing the mutation operation that will be executed.
* @param variables - the variables used to execute the operation.
* @param context - {@link OperationContext} options that'll be used in future exchanges.
* @returns A `Promise` of an {@link OperationResult} for the GraphQL mutation.
*
* @remarks
* The `mutation()` utility method is useful when your authentication requires you to make a GraphQL mutation
* request to update your authentication tokens. In these cases, you likely wish to bypass prior exchanges and
* the authentication in the `authExchange` itself.
*
* This method bypasses the usual mutation flow of the `Client` and instead issues the mutation as directly
* as possible. This also means that it doesn’t carry your `Client`'s default {@link OperationContext}
* options, so you may have to pass them again, if needed.
*/
mutate<Data = any, Variables extends AnyVariables = AnyVariables>(query: DocumentInput<Data, Variables>, variables: Variables, context?: Partial<OperationContext>): Promise<OperationResult<Data>>;
/** Adds additional HTTP headers to an `Operation`.
*
* @param operation - An {@link Operation} to add headers to.
* @param headers - The HTTP headers to add to the `Operation`.
* @returns The passed {@link Operation} with the headers added to it.
*
* @remarks
* The `appendHeaders()` utility method is useful to add additional HTTP headers
* to an {@link Operation}. It’s a simple convenience function that takes
* `operation.context.fetchOptions` into account, since adding headers for
* authentication is common.
*/
appendHeaders(operation: Operation, headers: Record<string, string>): Operation;
}
/** Configuration for the `authExchange` returned by the initializer function you write. */
interface AuthConfig {
/** Called for every operation to add authentication data to your operation.
*
* @param operation - An {@link Operation} that needs authentication tokens added.
* @returns a new {@link Operation} with added authentication tokens.
*
* @remarks
* The {@link authExchange} will call this function you provide and expects that you
* add your authentication tokens to your operation here, on the {@link Operation}
* that is returned.
*
* Hint: You likely want to modify your `fetchOptions.headers` here, for instance to
* add an `Authorization` header.
*/
addAuthToOperation(operation: Operation): Operation;
/** Called before an operation is forwaded onwards to make a request.
*
* @param operation - An {@link Operation} that needs authentication tokens added.
* @returns a boolean, if true, authentication must be refreshed.
*
* @remarks
* The {@link authExchange} will call this function before an {@link Operation} is
* forwarded onwards to your following exchanges.
*
* When this function returns `true`, the `authExchange` will call
* {@link AuthConfig.refreshAuth} before forwarding more operations
* to prompt you to update your authentication tokens.
*
* Hint: If you define this function, you can use it to check whether your authentication
* tokens have expired.
*/
willAuthError?(operation: Operation): boolean;
/** Called after receiving an operation result to check whether it has failed with an authentication error.
*
* @param error - A {@link CombinedError} that a result has come back with.
* @param operation - The {@link Operation} of that has failed.
* @returns a boolean, if true, authentication must be refreshed.
*
* @remarks
* The {@link authExchange} will call this function if it sees an {@link OperationResult}
* with a {@link CombinedError} on it, implying that it may have failed due to an authentication
* error.
*
* When this function returns `true`, the `authExchange` will call
* {@link AuthConfig.refreshAuth} before forwarding more operations
* to prompt you to update your authentication tokens.
* Afterwards, this operation will be retried once.
*
* Hint: You should define a function that detects your API’s authentication
* errors, e.g. using `result.extensions`.
*/
didAuthError(error: CombinedError, operation: Operation): boolean;
/** Called to refresh the authentication state.
*
* @remarks
* The {@link authExchange} will call this function if either {@link AuthConfig.willAuthError}
* or {@link AuthConfig.didAuthError} have returned `true` prior, which indicates that the
* authentication state you hold has expired or is out-of-date.
*
* When this function is called, you should refresh your authentication state.
* For instance, if you have a refresh token and an access token, you should rotate
* these tokens with your API by sending the refresh token.
*
* Hint: You can use the {@link fetch} API here, or use {@link AuthUtilities.mutate}
* if your API requires a GraphQL mutation to refresh your authentication state.
*/
refreshAuth(): Promise<void>;
}
/** Creates an `Exchange` handling control flow for authentication.
*
* @param init - An initializer function that returns an {@link AuthConfig} wrapped in a `Promise`.
* @returns the created authentication {@link Exchange}.
*
* @remarks
* The `authExchange` is used to create an exchange handling authentication and
* the control flow of refresh authentication.
*
* You must pass an initializer function, which receives {@link AuthUtilities} and
* must return an {@link AuthConfig} wrapped in a `Promise`.
* When this exchange is used in your `Client`, it will first call your initializer
* function, which gives you an opportunity to get your authentication state, e.g.
* from local storage.
*
* You may then choose to validate this authentication state and update it, and must
* then return an {@link AuthConfig}.
*
* This configuration defines how you add authentication state to {@link Operation | Operations},
* when your authentication state expires, when an {@link OperationResult} has errored
* with an authentication error, and how to refresh your authentication state.
*
* @example
* ```ts
* 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);
* }
* },
* };
* });
* ```
*/
declare function authExchange(init: (utilities: AuthUtilities) => Promise<AuthConfig>): Exchange;
export { AuthConfig, AuthUtilities, authExchange };