Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

oliverbusk's avatar

API - How to handle access- and refresh token

I am currently using a third-party API that offers "continuous access" to their API, by providing an access token and refresh token.

In order to get the access token and refresh token, I first need to generate an authorization code:

//1. Obtain an authorization_code
$grant = Http::asForm()->withToken($token)->post("https://api.tink.com/api/v1/oauth/authorization-grant", [
     'user_id' => auth()->user()->id,
     'scope' => 'accounts:read,balances:read,transactions:read,provider-consents:read'
]);

$authorizationCode = $grant['code'];

The above POST request will return an authorization code, which I will need to get the tokens:

//2. Get tokens
$params = [
            'code' => $authorizationCode,
            'client_secret'   => 'XXXXXXXX',
            'client_id'   => 'XXXXXXXX',
            'grant_type' => 'authorization_code',
 ];
$url = "https://api.tink.com/api/v1/oauth/token";
return Http::asForm()->post($url, $params)->json();

The above returns a JSON response, with:

{
    "access_token": "USER_ACCESS_TOKEN",
    "token_type": "bearer",
    "expires_in": 1800,
    "refresh_token": "REFRESH_TOKEN",
    "scope": "accounts:read,balances:read,transactions:read"
}

Now my question is: What should I do with this refresh- and access token?

I see that it expires every 30 minutes, so I can imagine that I need to generate a new authorizationCode every 30 minute for the user and get new tokens? But how to store these tokens per user - and how to automatically keep them refreshed?

0 likes
5 replies
martinbean's avatar
Level 80

@oliverbusk This is just OAuth. You’d handle it like any other OAuth token.

see that it expires every 30 minutes, so I can imagine that I need to generate a new authorizationCode every 30 minute for the user and get new tokens?

No. You get an access token with a refresh token and the number of seconds in which the token expires. So you need to make sure you refresh the token before that time. It’s also explained in the docs of that very API: https://docs.tink.com/entries/articles/retrieve-access-token#refresh-token

The access token will expire and needs to be refreshed for continued access. This is done with the refresh_token using the same endpoint as used when exchanging the authorization code for an access token.

So, store the refresh token somewhere and make sure you refresh it before the 1,800 seconds are up otherwise, yes, the access token will have expired and then you’ll need to go through the entire OAuth flow again.

1 like
oliverbusk's avatar

Thank you, Martin!

So if I understand correctly, I could store the refresh_token for the user in the database and have a expire_atcolumn that determines when the token will expire:

user_tink_tokens table:

id | user_id | refresh_token | expire_at
 1 | 1       | ezqlo........ | 2021/06/14 15:30
 1 | 2       | qwbai........ | 2021/06/14 16:00

Then, to keep it active/refreshed, have a cronjob that pulls all users with tokens that are set to expire within the next 5 minutes and refresh them?

martinbean's avatar

@oliverbusk You’d need to store the access token as well, otherwise how do you know which access token you’re refreshing? But yes, then create a scheduled task to refresh any soon-to-expire tokens, and save the new access token and refresh token for that user.

1 like
Vumatech's avatar

Storing it in a cache may reduce a db call that can be extra

Cache::remember("refresh_token,$refresh_token,$expires_in - $allowance_time) 

where $allowance_time is seconds you should allow it to mark as expired, but you can handle this in many way

2 likes
demobiel's avatar

@Vumatech this is an eyeopener... and not only in this scenario but more in general :) if something expires, there is no use to store it in a database...

In this case I used to store access_token, refresh_token and expires in the database with all kind of checks if I had to refresh the token or not. With this approach the cache just deals with all that logic: if it is expired it just no longer exists...

Thanks for this insight!

Please or to participate in this conversation.