トークンの有効期限が切れたら、refresh_tokenに基づいて新しいトークンを取得します。これはaxios.interceptors
で取得できることを読みました。
以下を確認してください。
Items
クラスの上に配置しましたか?.axios.interceptors.response
は、interceptor
変数に割り当てられます。この変数をどうすればよいですか?「axios.interceptors」に加えて、新しいトークンを取得する必要があります。トークンは24時間有効です。
ここにコード: https://stackblitz.com/edit/react-pkea41
import axios from 'axios';
axios.defaults.baseURL = localStorage.getItem('domain');
const interceptor = axios.interceptors.response.use(
response => response,
error => {
// Reject promise if usual error
if (errorResponse.status !== 401) {
return Promise.reject(error);
}
/*
* When response code is 401, try to refresh the token.
* Eject the interceptor so it doesn't loop in case
* token refresh causes the 401 response
*/
axios.interceptors.response.eject(interceptor);
return axios.post('/api/refresh_token', {
'refresh_token': JSON.parse(localStorage.getItem('token'))['refresh_token']
}).then(response => {
/*saveToken();*/
localStorage.setItem('token', JSON.stringify(response.data));
error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
return axios(error.response.config);
}).catch(error => {
/*destroyToken();*/
localStorage.setItem('token', '');
this.router.Push('/login');
return Promise.reject(error);
}).finally(createAxiosResponseInterceptor);
}
);
class Items extends Component {
constructor (props) {
super(props);
this.state = {
}
}
render () {
return (
<div >
</div>
)
}
}
render(<Items />, document.getElementById('root'));
Axios.interceptorsを正しく構成したかどうかを確認してください。
うまくいくと思います。ただし、慎重にテストすることをお勧めします。これは参照に適した記事です https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/
適切な場所、つまりtheItemsクラスの上に配置しましたか? ?
もちろん、AxiosとAPIの構成をラップするサービス関数を作成し、インターセプターを作成する必要があります
axios.interceptors.responseがインターセプター変数に割り当てられます。この変数をどうすればよいですか?
これは、インターセプターを定義するために使用される単なる変数です。気にしないで。割り当てを回避したい場合は、次のような関数内で実行してください axiosのインターセプターによるアクセストークンの更新の自動化
それが機能するかどうかをテストするには、24時間待つ必要がありますか、それとも別の方法で可能ですか?
LocalStorageに保存されているトークンを変更して、それを行うことができます
「client_id」、「secret_id」、「grant_type」はどこに置くべきですか?
LocalStorage内に保存すると、ページ内のスクリプトからアクセスできるようになります(XSS攻撃が外部の攻撃者にトークンへのアクセスを許可するのと同じくらい悪いのです)。
ローカルストレージ(またはセッションストレージ)に保存しないでください。ページに含めた3番目の部分のスクリプトのいずれかが侵害された場合、すべてのユーザーのトークンにアクセスできます。
JWTは、HTTPリクエストでのみサーバーに送信される特別な種類のCookieであるHttpOnly Cookie内に保存する必要があり、ブラウザーで実行されているJavaScriptから(読み取りと書き込みの両方で)アクセスすることはできません。
これは私が以前にやったことです。あなたの設定は私のものとは少し異なります。
const baseURL = localStorage.getItem('domain');
const defaultOptions = {
baseURL,
method: 'get',
headers: {
'Content-Type': 'application/json',
}
};
// Create Instance
const axiosInstance = axios.create(defaultOptions);
// Get token from session
const accessToken = ...
// Set the auth token for any request
instance.interceptors.request.use(config => {
config.headers.Authorization = accessToken ? `Bearer ${accessToken}` : '';
return config;
});
// Last step: handle request error general case
instance.interceptors.response.use(
response => response,
error => {
// Error
const { config, response: { status } } = error;
if (status === 401) {
// Unauthorized request: maybe access token has expired!
return refreshAccessToken(config);
} else {
return Promise.reject(error);
}
}
});
この部分はコンポーネントで分離する必要があると思います。ヘルパーまたはユーティリティに配置されます。また、refreshToken()メソッドは24時間前に呼び出されることはないため、24時間待つ必要があります。ここでclient_id
、secret_id
、grant_type
を処理する必要はありません。
Axios.interceptorsを正しく構成したかどうかを確認してください。
私が見ることができるものから、それはこの答えと同じなので、構成は問題ないようです https://stackoverflow.com/a/53294310/4229159
適切な場所、つまりtheItemsクラスの上に配置しましたか? ?
それは私が答えることができないことです、すべてのアプリケーションは異なります、それを置くのに最適な場所ではありませんが、例としては大丈夫かもしれません。 ただし、アプリではすべてのAPI呼び出しと一緒にする必要があります(例)
axios.interceptors.responseは、interceptor変数に割り当てられます。この変数をどうすればよいですか?
ご覧のとおり、/refresh_token
に割り当てられたconfig.headers['Authorization'] = 'Bearer ' + response.data.access_token;
への呼び出しから応答を得た変数は、バックエンドがそこから読み取る場合、正常なauth値を読み取ります。
それが機能するかどうかをテストするには、24時間待つ必要がありますか、それとも別の方法で可能ですか?
バックエンドが変更できない限り待機し、トークンをより短い時間で期限切れにする必要があります(例:5分または2分)
「client_id」、「secret_id」、「grant_type」はどこに置くべきですか?
パブリックなものでない限り、バックエンドにそれがあるはずです...おそらく、それがコールの構成に属しているかどうか、またはそれらを使用して認証しているかどうかを知るのが最善です。 それらを使用して認証し、それらがトークンを付与するものである場合、セキュリティリスクであるため、それをクライアント側に配置しないでください
1)構成は問題ありません。ただし、複数の並列リクエストがあり、それらすべてが同時に認証トークンを更新しようとしている場合、ソリューションは機能しません。これは問題を特定するのが本当に難しいと私が信じている。ですから、前もってカバーされるべきです。
2)いいえ。適切な場所ではありません。別のサービスを作成し(私はapi.serviceと呼びます)、それを使用してすべてのネットワーク/ API通信を行います。
3)インターセプター変数の使用はありません。変数への割り当てを回避できます。
4)APIを制御できる場合は、タイムアウトを少し減らすことができます。また、24時間は長すぎると思います。そうでなければ私は推測するオプションはありません。
5)それらに対処する必要があるかどうかわからない。
Bellowはapi.service.tsの作業コードです。それをアプリケーションに合わせるために、あちこちでいくつか変更する必要があるかもしれません。あなたがコンセプトを明確に理解すれば、それは難しくありません。また、複数の並列リクエストの問題もカバーしています。
import * as queryString from 'query-string';
import axios, { AxiosRequestConfig, Method } from 'axios';
import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values
var instance = axios.create({
baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';
export const apiService = {
get,
post,
put,
patch,
delete: deleteRecord,
delete2: deleteRecord2
}
function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}
function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}
function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}
function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}
function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}
function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
var url = createUrl(controller, action, urlParams, queryParams);
var options = createRequestOptions(url, method, data);
return instance.request<T>(options)
.then(res => res && res.data)
.catch(error => {
if (error.response) {
//handle error appropriately: if you want to display a descriptive error notification this is the place
} else {
//handle error appropriately: if you want to display a a generic error message
}
throw error;
});
}
function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
let url = controller + (action ? '/' + action : '');
urlParams.forEach(param => {
url += '/' + param;
});
let params = '';
if (queryParams) {
params += '?' + queryString.stringify(queryParams);
}
return url += params;
}
function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
var authToken = storageService.getAuthToken();
var jwtToken = authToken != null ? authToken.authToken : '';
var options: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Authorization': 'bearer ' + jwtToken
},
}
if (responseType) {
options.responseType = responseType;
}
return options;
}
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string = '') => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
}
instance.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.Push({ resolve, reject })
}).then(authToken => {
originalRequest.headers.Authorization = 'bearer ' + authToken;
return axios(originalRequest);
}).catch(err => {
return err;
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
accountService.refreshToken()
.then(result => {
if (result.succeeded) {
originalRequest.headers.Authorization = 'bearer ' + result.authToken;
axios(originalRequest).then(resolve, reject);
processQueue(null, result.authToken);
} else {
reject(error);
}
}).catch((err) => {
processQueue(err);
reject(err);
}).then(() => { isRefreshing = false });
});
}
return Promise.reject(error);
});
乾杯、