Token authentication interceptor
Interceptor
Before using extension hooks, make sure you are familiar with the basic use of alova.
Token authentication interceptor provides unified management of token-based login, logout, token assignment, and token refresh, and supports silent token refresh.
Features
- Unified maintenance of all codes for token identity authentication, including login, logout, token assignment, token refresh, etc.;
- Supports verification of token expiration on the client and server, and refreshes the token without any warning;
- Requests that rely on tokens automatically wait for the token refresh to complete before requesting;
- Set request ID with metadata;
- Automatically release visitor requests that do not rely on tokens;
Bind Token authentication interceptor
Token identity authentication is completed through global interceptors, which provide createClientTokenAuthentication
and createServerTokenAuthentication
for client- and server-based identity authentication respectively.
- Client-based identity authentication: means judging whether the token has expired from the client, such as the token expiration time obtained during login;
- Server-based identity authentication: It indicates whether the token has expired based on the status returned from the server. For example, when
status
is 401, it means it has expired;
Bind client-based authentication interceptor
import { createClientTokenAuthentication } from 'alova/client';
import { createAlova } from 'alova';
const { onAuthRequired, onResponseRefreshToken } = createClientTokenAuthentication({
// ...
});
const alovaInstance = createAlova({
// ...
beforeRequest: onAuthRequired(method => {
// ...interceptor before original request
}),
responded: onResponseRefreshToken((response, method) => {
//...original response success interceptor
return response.json();
})
});
In onResponseRefreshToken
, you can also bind response error and completion interceptors, which is the same as the original usage.
createAlova({
// ...
responded: onResponseRefreshToken({
onSuccess: (response, method) => {
//...original response success interceptor
},
onError: (error, method) => {
//...original response error interceptor
},
onComplete: method => {
//...original response completion interceptor
}
})
});
If you don't need to set an interceptor, you don't need to pass in the interceptor function.
createAlova({
//...
beforeRequest: onAuthRequired(),
responded: onResponseRefreshToken()
});
Bind server-based authentication interceptor
Same as client-based usage
import { createServerTokenAuthentication } from 'alova/client';
import { createAlova } from 'alova';
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({
// ...
});
const alovaInstance = createAlova({
// ...
beforeRequest: onAuthRequired(method => {
// ...interceptor before original request
}),
responded: onResponseRefreshToken((response, method) => {
//...original response success interceptor
return response.json();
})
});
When you use the alova/fetch
adapter, you may encounter the problem TypeError: Failed to execute 'json' on 'Response': body stream already read
. This is because the body stream
of Response
can only be accessed once, you can response.clone().json()
to solve it.
Refresh Token silently on the client
Just set refreshToken
and specify whether the token expires, and call the function to refresh the token. When the token refresh is completed, all requests that rely on the token will wait for the token refresh to complete.
createClientTokenAuthentication({
refreshToken: {
// Triggered before the request, the method parameter will be received and a boolean will be returned to indicate whether the token has expired.
isExpired: method => {
return tokenExpireTime < Date.now();
},
// Triggered when the token expires, trigger the refresh token in this function
handler: async method => {
try {
const { token, refresh_token } = await refreshToken();
localStorage.setItem('token', token);
localStorage.setItem('refresh_token', refresh_token);
} catch (error) {
// redirect to login page once token refresh failed.
location.href = '/login';
// and must throw error.
throw error;
}
}
}
});
- In order for the
refreshToken
request to pass smoothly, theauthRole
needs to be marked asrefreshToken
through metadata. - If the token refresh fails, an error must be thrown to prevent the failed API from retrying and the waiting APIs from continuing the request.
For more information about metadata, go to method metadata.
export const refreshToken = () => {
const method = alovaInstance.Get('/refresh_token');
method.meta = {
authRole: 'refreshToken'
};
return method;
};
Refresh Token silently on the server side
The same as refreshing the token silently on the client, just specify whether the token expires and call the function to refresh the token. When the token refresh is completed, all requests that rely on the token will wait for the token refresh to complete.
Processed in request success interceptor
When using alova/fetch
, as long as the server returns the response data, the response success interceptor will be triggered. At this time, we need to handle the token refresh in the response success interceptor.
createServerTokenAuthentication({
refreshTokenOnSuccess: {
// Triggered when responding, the response and method can be obtained, and a boolean is returned to indicate whether the token has expired.
// When the server returns 401, it means the token has expired.
isExpired: (response, method) => {
return response.status === 401;
},
// Triggered when the token expires, trigger the refresh token in this function
handler: async (response, method) => {
try {
const { token, refresh_token } = await refreshToken();
localStorage.setItem('token', token);
localStorage.setItem('refresh_token', refresh_token);
} catch (error) {
// redirect to login page once token refresh failed.
location.href = '/login';
// and must throw error.
throw error;
}
}
}
});
Handled in request error interceptor
When using the axios
interceptor, if the server returns a status code other than 200/300
, the response error interceptor will be triggered. At this time, we need to handle the token refresh in the response error interceptor.
createServerTokenAuthentication({
refreshTokenOnError: {
// Triggered when responding, error and method can be obtained, and boolean is returned to indicate whether the token has expired.
// When the server returns 401, it means the token has expired.
isExpired: (error, method) => {
return error.response.status === 401;
},
// Triggered when the token expires, trigger the refresh token in this function
handler: async (error, method) => {
try {
const { token, refresh_token } = await refreshToken();
localStorage.setItem('token', token);
localStorage.setItem('refresh_token', refresh_token);
} catch (error) {
// redirect to login page once token refresh failed.
location.href = '/login';
// and must throw error.
throw error;
}
}
}
});
- In order for the
refreshToken
request to pass smoothly, theauthRole
needs to be marked asrefreshToken
through metadata. - If the token refresh fails, an error must be thrown to prevent the failed API from retrying and the waiting APIs from continuing the request.
For more information about metadata, please go to method metadata.
export const refreshToken = () => {
const method = alovaInstance.Get('/refresh_token');
method.meta = {
authRole: 'refreshToken'
};
return method;
};
Release visitor request
Some interfaces do not need to rely on token authentication. We call them "guest requests". At this time, we can set their metadata to authRole: null
to bypass the front-end interception and allow them to send requests and receive responses smoothly.
export const requestTokenNotRequired = () => {
const method = alovaInstance.Get('/token_not_required');
method.meta = {
authRole: null
};
return method;
};
Login interception
In the identity authentication interceptor, you can also intercept login requests and save login information in the interceptor to achieve the purpose of unified maintenance of identity authentication codes.
First identify the metadata of the login request as authRole: 'login'
.
export const login = () => {
const method = alovaInstance.Get('/login');
method.meta = {
authRole: 'login'
};
return method;
};
Then save the login information in the login interceptor.
createClientTokenAuthentication({
login(response, method) {
localStorage.setItem('token', response.token);
localStorage.setItem('refresh_token', response.refresh_token);
}
});
The login interceptor usage of
createServerTokenAuthentication
is the same.
Assign token
Usually, we will append token to the request information in beforeRequest
. The assignToken
callback function is provided in the Token authentication interceptor for assigning tokens. It will filter guest requests and login requests and trigger them before the requests. It can also achieve the purpose of unified maintenance of identity authentication codes.
createClientTokenAuthentication({
assignToken: method => {
method.config.headers.Authorization = localStorage.getItem('token')
},
});
The usage of assignToken callback function of
createServerTokenAuthentication
is the same.
Logout interception
When your logout also requires calling the interface, you can also intercept the logout request and clear the login information.
First identify the logout request metadata as authRole: 'logout'
.
export const logout = () => {
const method = alovaInstance.Get('/logout');
method.meta = {
authRole: 'logout'
};
return method;
};
Then clear the login information in the logout interceptor.
createClientTokenAuthentication({
logout(response, method) {
localStorage.removeItem('token');
localStorage.removeItem('refresh_token');
}
});
The login interceptor usage of
createServerTokenAuthentication
is the same.
Custom identification identity
The above metadata identities are actually the default identities. If you need to customize the identity, you can set it as follows.
token refresh identity
createClientTokenAuthentication({
refreshToken: {
metaMatches: {
refreshToken: true
}
// ...
}
});
createServerTokenAuthentication({
refreshTokenOnSuccess: {
metaMatches: {
refreshToken: true
}
// ...
},
refreshTokenOnError: {
metaMatches: {
refreshToken: true
}
// ...
}
});
Then, requests with refreshToken: true
in the metadata will be identified as refreshToken
.
export const refreshToken = () => {
const method = alovaInstance.Get('/refresh_token');
method.meta = {
refreshToken: true
};
return method;
};
Visitor identify
createClientTokenAuthentication({
visitorMeta: {
isVisitor: true
}
});
Then, requests with isVisitor: true
in the metadata will be identified as visitors.
export const requestTokenNotRequired = () => {
const method = alovaInstance.Get('/token_not_required');
method.meta = {
isVisitor: true
};
return method;
};
Login identity
createClientTokenAuthentication({
login: {
metaMatches: {
login: true
},
handler(response, method) {
//Login interceptor
}
}
});
Then, requests with login: true
in the metadata will be identified as login
identity.
export const login = () => {
const method = alovaInstance.Get('/login');
method.meta = {
login: true
};
return method;
};
Log out identity
createClientTokenAuthentication({
logout: {
metaMatches: {
logout: true
},
handler(response, method) {
//Logout interceptor
}
}
});
Then, requests with logout: true
in the metadata will be identified as logout
.
export const logout = () => {
const method = alovaInstance.Get('/logout');
method.meta = {
logout: true
};
return method;
};
The login interceptor usage of
createServerTokenAuthentication
is the same.
Typescript
By default, createClientServerTokenAuthentication
and createServerTokenAuthentication
are adapted to the alova/fetch
request adapter, you can only specify the type of StatesHook
, as follows:
const { onAuthRequired, onResponseRefreshToken } = createClientTokenAuthentication<
typeof VueHook
>({
//...
});
const alovaInstance = createAlova({
// ...
beforeRequest: onAuthRequired(method => {
// The type of method is Method<any, any, any, any, RequestInit, Response, Headers>
}),
responded: onResponseRefreshToken((response, method) => {
//The response type is Response
return response.json();
})
});
If you are not using the alova/fetch
request adapter, You also need to specify the type of request adapter, which is also simple.
The following is an example of the axios request adapter. Specify the request adapter type in createClientTokenAuthentication
.
import { axiosRequestAdapter } from '@alova/adapter-axios';
const { onAuthRequired, onResponseRefreshToken } = createClientTokenAuthentication<
typeof ReactHook,
typeof axiosRequestAdapter
>({
//...
});
const alovaInstance = createAlova({
//...
beforeRequest: onAuthRequired(method => {
// The type of method is Method<any, any, any, any, AlovaAxiosRequestConfig, AxiosResponse, AxiosResponseHeaders>
}),
responded: onResponseRefreshToken((response, method) => {
//The response type is AxiosResponse
return response.data;
})
});
The server-based Token authentication interceptor is used in the same way.
import { axiosRequestAdapter } from '@alova/adapter-axios';
createServerTokenAuthentication<typeof ReactHook, typeof axiosRequestAdapter>({
//...
});