Discussion Frontier Oauth service Refresh Tokens expiring unexpectedly

Context​


I'm trying to extend tools like ICARUS Terminal and Ardent for CMDRs but I'm running into unexpected and undocumented behavior with Refresh Tokens issues from the Frontier Oauth service. I would love some insight from someone at Frontier, or first experiences from other developers who have used this API and either experienced this same issue, or not experienced it. I've also tried reaching out on the EDCD Discord about this issue.

What is working​


Getting Oauth authentication working was straight forward and getting an updated Access Token (which also returns an updated Refresh Token) is simple, the code looks like this:

JavaScript:
const response = await fetch('https://auth.frontierstore.net/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: formData({ /* This function converts the object into form data */
      'client_id': AUTH_CLIENT_ID,
      'grant_type': 'refresh_token',
      'refresh_token': refreshToken
    })
  })
  const responsePayload = await response.json()

The Access Tokens have an expiry time of 4 hours after being issued and the pattern is to use a single use Refresh Token to get a new one after that time (and to persist the new single use Refresh Token for the next time you need to do this). I can repeatedly refresh an Access Token and Refresh Token pair, as long as the Access Token itself has not expired; that works great.

What isn't working​


I cannot use the Refresh Token after Access Token itself expires.

The Refresh Token acts as a simple session token (i.e. it's just a string and must be persisted in a datastore server side by Frontier, it is not itself a JWT) and it appears that Frontier's Oauth implementation is not persisting the Refresh Tokens at their end, after ~4 hours the endpoint then returns:

JavaScript:
{
  error: 'invalid_token',
  error_description: 'The access token expired or is not found'
}

The Access Token is not part of the request to refresh to the token, which makes this a slightly odd error. I suspect either by design or by mistake the Frontier Oauth flow just doesn't let you use Refresh Tokens to persist a session if the Access Token isn't actively being used.

Workarounds​


I would rather not work around this by persisting Access Tokens server side and doing a keep alive on them every few hours to work around this (currently they are stored securely in an JWT on the client) but presently that is the only way I have to allow users to stay signed in for more than 4 hours - I am issuing my own JWT's with their own expiry time so I can persist a session longer.

The reputed 25 day max length for a session seems very reasonable but 4 hours is a very short expiry time and would require someone to sign in again every day, which is a bit much as user experience goes. One thing that might be contributing factor is that I'm using the PKCE only flow and not providing a secret, because there doesn't appear to be a way to request a secret via the developer portal (and they are not issued automatically); it occurs to me this could be intentional behavior to mitigate inappropriate usage of the APIs, to avoid the overhead of having to police usage - or it could be unintended behavior.

I'm unclear if Frontier's implementation behaves differently if using a PKCE only flow versus being used with a shared secret - I would be interested to learn about direct experiences with others using the API to know if they had similar or divergent experiences.

The implementation I've written is open source, and all the other open source implementations I could find follow the same pattern, so unless I'm missing something I assume other developers have also run into this issue.
 
Last edited:
The only difference I can see so far between implementations is that redirect_uri does not need to be there - although was present in sample code I found it could maybe be triggering a bug, given the /token endpoint also handles logic for other requests (e.g. like getting an access token in the first place).

I've tried removing that parameter and will see how that gets on in a few hours.


With the extra parameter removed, the Refresh Token was still rejected 4 hours after it was issued (like the Access Token), which makes no sense as intended behavior. The Refresh Token is only working for as long as the Access Token is valid.

I've updated the code example above to reflect what the payload looks like for clarity, and can confirm the scope is (and always has been) auth capi and that the /decode end point confirms this, and that the Access Tokens are rotated with new iat and exp values when they get updated.

The issue is the /token endpoint just stops letting tokens be rotated with a Refresh Token after the Access Token also expires, but works until that point - and the process can be repeated multiple times to get new Access Token and Refresh Token, until the most recently issued Access Token expires.

TL;DR /token is not working as expected for rotation, it is for other developers. This might be related to my having a newer developer account (as I haven't needed one for ICARUS Terminal or Ardent to this point, as they have so far relied on on Journal files and EDDN respectively).
 
Last edited:
Okay! So after some much appreciated help from other developers on the EDCD Discord who Refresh Tokens were working as expected for, which was a little baffling, I think I figured out what's going on.

It seems signing into the same third party app (i.e. to the same Client ID) with the same account multiple times actually causes older previously issued Refresh Tokens to stop working, at least after the Access Tokens they are associated with expire after 4 hours.

This behavior is not explicitly documented anywhere though other providers like Google do have limits - in Google's case it's 100 Refresh Tokens per Client ID. I was signing in on my desktop, laptop and phone (and possibly a couple of other test sessions) and I'm guessing that the limit on Frontier is just lower, possibly even just one active session per User for each app/Client ID, which isn't too problematic for an app but a bit restrictive for a website, unless you store the Access Token and Refresh Token server side (and key your own sessions to them).

I was hoping to keep things stateless on my side but I guess if I want to try and remake an ICARUS Terminal style experience in a browser I'll probably need to store user tokens server side; but at least I have some idea of what's been going on!
 
Back
Top Bottom