REST Authentication in Yii2

Dear all,

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,

  1. Does yii will handle the token checking automatically?
  2. if not automatic, how should I manage the token?
  3. how should I check the incoming request for valid token?
  4. regenerate new token for every incoming request?.
  5. invalidate when user logout?

Thank you in advance

Hm, I can tell what I do in my APIs.

  1. Yes, Authentication
  2. As you want :slight_smile:
  3. Use HttpBearerAuth or HttpHeaderAuth
  4. Probably yes - it’s easier and more client-friendly
  5. Yes I mark my tokens as expired
1 Like

Dear TomaszKane,

Thank you for pointing out the article. I am still not quite understand,

  1. Does HttpBearerAuth will do automatic checking token in every request? and I only need to provide method findIdentityByAccessToken() ?

  2. regenerate new token for every incoming request ==> how can I replace the token on every request?

Thank you again

  1. Yes HttpBearerAuth will check for valid data during every request and search for your user via findIdentityByAccessToken

  2. 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
    ];
}

Thank you so much for your insight. I never think about that before.