Would you mind helping me in implementing authentication for my REST API endpoint ? I am confused on the best way we should implement the auth. The frontend would be an angular project. The flow for login is the FE will supply username and password in their payload and I response with access_token.
I do not want this access_token to be valid forever, so I create a table user_token and managed the token there.
The questions,
Does yii will handle the token checking automatically?
if not automatic, how should I manage the token?
how should I check the incoming request for valid token?
Yes HttpBearerAuth will check for valid data during every request and search for your user via findIdentityByAccessToken
unless you only have a very few requests this would be insane.
Especially on webapps with many requests at the beginning - 10-20 are easily reached for dashboards with charts and such - it will lead to errors.
When you have multiple requests at the same time, the server will invalidate the rest of them after the first one creates a new token.
Let’s pretend your first request creates an accessToken A, then you make 2 simultaneous requests to fetch data
request for route A with AccessToken A → the user will have a new Token B
request for route B with AccessToken A → error wrong accessToken provided
request for route C that maybe waited a little bit. now with accessToken B → creates accessToken C
You see the problem?
I would only create a new token when the user logs out or a certain time is reached.
You can create 2 tokens. An accessToken and a refreshToken.
If you use libraries like axios this is easily done, they have so called Interceptors |Â Axios Docs that can handle it.
I made something similar with my application, when a request is done to a certain endpoint/route that requires an “elevated session” I send a certain HTTP response code (higher login required). You could basically do the same in your findIdentityByAccessToken function in case an old accessToken is provided.
// pseudocode
$user = User::find()->where(['accessToken' => $accessToken])->one();
if($user === null){
$oldToken = User:.find()->where(['oldAccessToken' => $accessToken])->one();
if($oldToken){
// they send an old token
throw new HttpException(xxx, 'Old accessToken found');
}
}
Then your axios client could do the following
apiClient.interceptors.response.use(
(response) => response,
async error => {
const {config, response: {status, data: {code = null}}} = error;
// check for your "old accessToken" status
if (status === xxx) {
axios.post('users/refresh-token', {
refreshToken: 'your-refresh-token'
}).then(res => {
// set the new accessToken for the ogirinal request
originalRequest.headers['accessToken'] = res.data.accessToken;
// run the original request that was blocked due to an old token again as if nothing happened
// the user won't know their accessToken has changed, but only this client will have the new one
// all other devices are logged out, because they don't have the new token and no new refreshToken
resolve(apiClient.request(originalRequest));
});
}
}
Your refreshToken would then create a new Token
public function actionRefreshToken()
{
$user = User::find()->where(['refreshToken' => $this->request->getBodyParam('refreshToken'))->one();
$user->oldAccessToken = $user->accessToken;
$user->refreshToken = build a new token;
$user->accessToken = build a new token;
// save your user
// return both tokens
return [
'accessToken' => $user->accessToken,
'refreshToken' => $user->refreshToken
];
}