external-auth-server
external-auth-server copied to clipboard
[BUG] uncaughtException: Cannot read properties of undefined (reading 'match')
Heyo!
I have a EAS installation running in a kubernetes cluster in HA-mode.
I have multiple providers/configurations hooked up to a keycloak instance for authenticating users for a SPA.
Direct sign-in using a keycloak user works. Signing in with a SSO backed by another keycloak instance also works. But a final SSO backed by Active directory seems to cause the EAS to crash with the following error:
uncaughtException: Cannot read properties of undefined (reading 'match')
TypeError: Cannot read properties of undefined (reading 'match')
at Object.parse (/home/eas/app/node_modules/uri-js/dist/es5/uri.all.js:874:29)
at OpenIdConnectPlugin.get_authorization_redirect_uri (/home/eas/app/src/plugin/oauth/index.js:2514:27)
at handle_auth_callback_request (/home/eas/app/src/plugin/oauth/index.js:1649:45)
at OpenIdConnectPlugin.verify (/home/eas/app/src/plugin/oauth/index.js:1924:29)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async processPipeline (/home/eas/app/src/server.js:399:13)
I changed the log-level to silly and reproduced the error:
[
{
"jsonPayload": {
"message": "verify request details: {\"url\":\"/verify?config_token_store_id=<id>&config_token_id_query_engine=jq&config_token_id_query=.parentRequestInfo.parsedUri.host+%7C+split%28%22.%22%29%5B0%5D\",\"params\":{},\"query\":{\"config_token_store_id\":\"<id>\",\"config_token_id_query_engine\":\"jq\",\"config_token_id_query\":\".parentRequestInfo.parsedUri.host | split(\\\".\\\")[0]\"},\"http_method\":\"GET\",\"http_version\":\"1.1\",\"headers\":{\"host\":\"<host>.svc.cluster.local\",\"user-agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"accept-encoding\":\"gzip, deflate, br\",\"accept-language\":\"nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7\",\"cache-control\":\"max-age=0\",\"cookie\":\"_hjSessionUser_3324444=eyJ***fQ==; _pk_id.4.0605=ba5*****7.1677162909.\",\"sec-ch-ua\":\"\\\"Chromium\\\";v=\\\"110\\\", \\\"Not A(Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"110\\\"\",\"sec-ch-ua-mobile\":\"?0\",\"sec-ch-ua-platform\":\"\\\"Windows\\\"\",\"sec-fetch-dest\":\"document\",\"sec-fetch-mode\":\"navigate\",\"sec-fetch-site\":\"cross-site\",\"upgrade-insecure-requests\":\"1\",\"x-forwarded-for\":\"10.0.0.1\",\"x-forwarded-host\":\"<domain>.com\",\"x-forwarded-method\":\"GET\",\"x-forwarded-port\":\"443\",\"x-forwarded-proto\":\"https\",\"x-forwarded-server\":\"traefik-production-869bc86644-vzp7s\",\"x-forwarded-uri\":\"/?__eas_oauth_handler__=authorization_callback&state=43ff******b546&session_state=c1****-****-****-****-*********59c&code=7*******-****-****-****-***********6d.c1*****-****-****-****-*********59c.e*****-****-****-****-**********40b\",\"x-real-ip\":\"10.0.0.1\"},\"body\":{}}",
"level": "silly",
"timestamp": "2023-03-09T06:57:16.354Z",
"service": "external-auth-server"
}
},
{
"jsonPayload": {
"message": "starting verify pipeline",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.354Z",
"level": "info"
}
},
{
"jsonPayload": {
"timestamp": "2023-03-09T06:57:16.354Z",
"message": "verify params: {\"config_token_store_id\":\"<id>\",\"config_token_id_query_engine\":\"jq\",\"config_token_id_query\":\".parentRequestInfo.parsedUri.host | split(\\\".\\\")[0]\"}",
"level": "silly",
"service": "external-auth-server"
}
},
{
"jsonPayload": {
"message": "server-side config_token_id query info - query: .parentRequestInfo.parsedUri.host | split(\".\")[0], query_engine: jq, data: {\"req\":{\"headers\":{\"host\":\"<host>.svc.cluster.local\",\"user-agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\",\"accept-encoding\":\"gzip, deflate, br\",\"accept-language\":\"nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7\",\"cache-control\":\"max-age=0\",\"cookie\":\"_hjSessionUser_3324444=ey******Q==; _pk_id.4.0605=b****477.1677162909.\",\"sec-ch-ua\":\"\\\"Chromium\\\";v=\\\"110\\\", \\\"Not A(Brand\\\";v=\\\"24\\\", \\\"Google Chrome\\\";v=\\\"110\\\"\",\"sec-ch-ua-mobile\":\"?0\",\"sec-ch-ua-platform\":\"\\\"Windows\\\"\",\"sec-fetch-dest\":\"document\",\"sec-fetch-mode\":\"navigate\",\"sec-fetch-site\":\"cross-site\",\"upgrade-insecure-requests\":\"1\",\"x-forwarded-for\":\"10.0.0.1\",\"x-forwarded-host\":\"<domain>.com\",\"x-forwarded-method\":\"GET\",\"x-forwarded-port\":\"443\",\"x-forwarded-proto\":\"https\",\"x-forwarded-server\":\"traefik-production-869bc86644-vzp7s\",\"x-forwarded-uri\":\"/?__eas_oauth_handler__=authorization_callback&state=43****&session_state=c1...&code=7a....c1...4\",\"x-real-ip\":\"10.0.0.1\"},\"cookies\":{\"_hjSessionUser_3324444\":\"ey...Q==\",\"_pk_id.4.0605\":\"ba59...9.\"},\"signedCookies\":{},\"query\":{\"config_token_store_id\":\"dap2\",\"config_token_id_query_engine\":\"jq\",\"config_token_id_query\":\".parentRequestInfo.parsedUri.host | split(\\\".\\\")[0]\"},\"method\":\"GET\",\"httpVersionMajor\":1,\"httpVersionMinor\":1,\"httpVersion\":\"1.1\"},\"parentRequestInfo\":{\"uri\":\"https://<domain>.com/?__eas_oauth_handler__=authorization_callback&state=4...&session_state=c...&code=7...\",\"parsedUri\":{\"scheme\":\"https\",\"host\":\"<domain>.com\",\"path\":\"/\",\"query\":\"__eas_oauth_handler__=authorization_callback&state=4...&session_state=c...&code=...\",\"reference\":\"absolute\"},\"parsedQuery\":{\"__eas_oauth_handler__\":\"authorization_callback\",\"code\":\"7...\",\"session_state\":\"c...\",\"state\":\"4...\"},\"method\":\"GET\"},\"parentReqInfo\":{\"uri\":\"https://<domain>.com/?__eas_oauth_handler__=authorization_callback&state=4...46&session_state=c...c&code=7...0b\",\"parsedUri\":{\"scheme\":\"https\",\"host\":\"<domain>.com\",\"path\":\"/\",\"query\":\"__eas_oauth_handler__=authorization_callback&state=4...6&session_state=ci...&code=7...b\",\"reference\":\"absolute\"},\"parsedQuery\":{\"__eas_oauth_handler__\":\"authorization_callback\",\"code\":\"7...b\",\"session_state\":\"c1...c\",\"state\":\"4...6\"},\"method\":\"GET\"}}",
"service": "external-auth-server",
"level": "debug",
"timestamp": "2023-03-09T06:57:16.355Z"
}
},
{
"jsonPayload": {
"level": "info",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.389Z",
"message": "sever-side token: store=<id>, id=<domain>"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"level": "debug",
"timestamp": "2023-03-09T06:57:16.389Z",
"message": "adapter config: {\"adapter\":\"sql\",\"options\":{\"config\":{\"client\":\"pg\",\"connection\":{\"database\":\"eas_db\",\"host\":\"10.87.64.3\",\"password\":\"2******\",\"user\":\"******\"},\"pool\":{\"idleTimeoutMillis\":60000,\"max\":10,\"min\":0}},\"query\":\"SELECT token AS token FROM config_tokens WHERE tenant = ? AND client_id = '<id>' AND revoked = False\"}}"
}
},
{
"jsonPayload": {
"message": "cache key: config_token_adapater:sql:connections:c4ff***********3c",
"timestamp": "2023-03-09T06:57:16.389Z",
"service": "external-auth-server",
"level": "verbose"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.390Z",
"level": "verbose",
"message": "cached SQL connection"
}
},
{
"jsonPayload": {
"timestamp": "2023-03-09T06:57:16.413Z",
"level": "debug",
"service": "external-auth-server",
"message": "server-side config token: e....I"
}
},
{
"jsonPayload": {
"level": "debug",
"timestamp": "2023-03-09T06:57:16.414Z",
"message": "config token: {\"eas\":{\"plugins\":[{\"type\":\"oidc\",\"issuer\":{\"discover_url\":\"https://auth.<domain>/auth/realms/<id>/.well-known/openid-configuration\"},\"client\":{\"client_id\":\"<id>\",\"client_secret\":\"8****1\"},\"scopes\":[\"openid\",\"email\",\"profile\"],\"features\":{\"cookie_expiry\":true,\"userinfo_expiry\":true,\"session_expiry\":172800,\"session_expiry_refresh_window\":86400,\"session_retain_id\":true,\"refresh_access_token\":true,\"fetch_userinfo\":true,\"introspect_access_token\":false,\"authorization_token\":\"access_token\"},\"assertions\":{\"exp\":true,\"nbf\":true,\"iss\":true,\"userinfo\":[],\"id_token\":[]},\"cookie\":{\"name\":\"_<name>_session\",\"path\":\"/\"},\"xhr\":{\"redirect_http_code\":401,\"use_referer_as_redirect_uri\":true},\"csrf_cookie\":{\"enabled\":false},\"custom_error_headers\":{},\"custom_service_headers\":{\"X-Token\":{\"source\":\"access_token\",\"query_engine\":\"jp\",\"query\":\"$\",\"query_opts\":{\"single_value\":true}}}}]},\"iat\":1673253928,\"audMD5\":\"216********b3\"}",
"service": "external-auth-server"
}
},
{
"jsonPayload": {
"message": "starting verify for plugin: oidc",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.414Z",
"level": "info"
}
},
{
"jsonPayload": {
"timestamp": "2023-03-09T06:57:16.414Z",
"service": "external-auth-server",
"level": "verbose",
"message": "parent request info: {\"uri\":\"https://<domain>.com/?__eas_oauth_handler__=authorization_callback&state=4....46&session_state=c...9c&code=7***0b\",\"parsedUri\":{\"scheme\":\"https\",\"host\":\"<domain>.com\",\"path\":\"/\",\"query\":\"__eas_oauth_handler__=authorization_callback&state=4...6&session_state=c...c&code=7...b\",\"reference\":\"absolute\"},\"parsedQuery\":{\"__eas_oauth_handler__\":\"authorization_callback\",\"code\":\"7...b\",\"session_state\":\"c...c\",\"state\":\"4....6\"},\"method\":\"GET\"}"
}
},
{
"jsonPayload": {
"level": "verbose",
"message": "audMD5: 2....3",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.414Z"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"level": "verbose",
"timestamp": "2023-03-09T06:57:16.414Z",
"message": "cookie name: _dap2_session"
}
},
{
"jsonPayload": {
"message": "decocded state pointer: {\"request_uri\":\"https://<domain>.com/\",\"aud\":\"2...3\",\"csrf\":\"c...0\",\"req\":{\"headers\":{}},\"request_is_xhr\":false,\"iat\":167688102}",
"service": "external-auth-server",
"level": "verbose",
"timestamp": "2023-03-09T06:57:16.415Z"
}
},
{
"jsonPayload": {
"level": "verbose",
"message": "retrieving state: state:oauth:undefined",
"timestamp": "2023-03-09T06:57:16.415Z",
"service": "external-auth-server"
}
},
{
"jsonPayload": {
"timestamp": "2023-03-09T06:57:16.418Z",
"service": "external-auth-server",
"message": "retrieved encrypted state content: null",
"level": "verbose"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"level": "verbose",
"timestamp": "2023-03-09T06:57:16.418Z",
"message": "failed to decrypt state"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"message": "decoded state: false",
"level": "verbose",
"timestamp": "2023-03-09T06:57:16.418Z"
}
},
{
"jsonPayload": {
"message": "audMD5: 2....3",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.418Z",
"level": "verbose"
}
},
{
"jsonPayload": {
"timestamp": "2023-03-09T06:57:16.418Z",
"level": "verbose",
"service": "external-auth-server",
"message": "cookie name: _dap2_session"
}
},
{
"jsonPayload": {
"message": "begin token fetch with authorization code",
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.418Z",
"level": "verbose"
}
},
{
"jsonPayload": {
"stack": "TypeError: Cannot read properties of undefined (reading 'match')\n at Object.parse (/home/eas/app/node_modules/uri-js/dist/es5/uri.all.js:874:29)\n at OpenIdConnectPlugin.get_authorization_redirect_uri (/home/eas/app/src/plugin/oauth/index.js:2514:27)\n at handle_auth_callback_request (/home/eas/app/src/plugin/oauth/index.js:1649:45)\n at OpenIdConnectPlugin.verify (/home/eas/app/src/plugin/oauth/index.js:1924:29)\n at runMicrotasks (<anonymous>)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at async processPipeline (/home/eas/app/src/server.js:399:13)",
"timestamp": "2023-03-09T06:57:16.418Z",
"message": "Cannot read properties of undefined (reading 'match')",
"level": "error",
"service": "external-auth-server"
}
},
{
"jsonPayload": {
"service": "external-auth-server",
"timestamp": "2023-03-09T06:57:16.419Z",
"message": "end verify pipeline with status: 503",
"level": "info"
}
},
]
Surely I'm simply doing something silly here with the SSO configuration that i have overlooked. Though having trouble pin-pointing exactly what is causing it.
In addition, i think a check is missing here:
https://github.com/travisghansen/external-auth-server/blob/master/src/plugin/oauth/index.js#L1919
Either as a check for null/undefined on the decodedStatePointer.state_id property
Or as a check on the resulting statePayload....
More will follow as i dive deeper... In the meantime any advice?
This one will be tricky. Indeed I (from another bug) already have code queued up to detect a null state and return an 403 immediately so don’t worry about that one. The question is assuming a relatively quick login process why is the state not there?
Wait is this occurring after a logout process has occurred?
To the error occur when:
- Navigating to domain.
- Getting redirected to keycloak for signing in.
- Clicking the SSO button as shown in the image

- Signing in on the third party
- Being redirected back to the original site.
Here, the EAS blows up, and 503 is returned

Does it do that same behavior on fresh incognito window? Looking through the code the only scenario I saw where the state data gets stored as not the state_id is during a logout situation (another bug).
I just double checked...
- Signing in with the username/password works
- Signing in with first SSO (another keycloak using OIDC) works
- Signing in with second SSO (active directory SAML) not working....
I'm quite sure that its the setup of the provider, in relation to the config token that is the issue.
Since the everything works, to the point of the redirect back to the original site, where the EAS is blowing up, I think its the config token that must be the issue here
One sec.... ill check if incognito will help :)
I can confirm.... the problem is not persistent. It is part of an sign-out and back in that seems to only happen sometimes...
Any way to work around it, other then clearing cookies and app-data...?
Or even better a permanent fix?
Yeah, I'll fix it up and snap a new release.
Cool, thanks.... Need any help with that?
or anything i can do i the meantime?
Nah, might be a day or 2 though.
Fair enough! :) I'll be waiting like a kid for Christmas, if nothing else, i'll be ready to test it when you have it ready
Thanks agian for the quick response
LOL. Can you send your cleansed config token? You're using some pretty advanced stuff (that I don't test often, but also doesn't change often) so I want to make sure I'm covering the use-case.
This here is the template that we have made.... We are using it to programmatically generate config_tokens so we can have it as IAC:
{
"eas": {
"plugins": [{
"type": "oidc",
"issuer": {
"discover_url": f"{discovery_url_base}/auth/realms/{client.realm}/.well-known/openid-configuration"
},
"client": {
"client_id": client.id,
"client_secret": client.secret
},
"scopes": ["openid", "email", "profile"],
"features": {
# how to expire the cookie
# true = cookies expire will expire with tokens
# false = cookies will be 'session' cookies
# num seconds = expire after given number of second
"cookie_expiry": True,
# how frequently to refresh userinfo data
# true = refresh with tokens(assuming they expire)
# false = never refresh
# num seconds = expire after given number of seconds
"userinfo_expiry": True,
# how long to keep a session (server side) around
# true = expire with tokenSet (if applicable)
# false = never expire
# num seconds = expire after given number of seconds (enables sliding window)
#
# sessions become a floating window *if*
# - tokens are being refreshed
# or
# - userinfo being refreshed
# or
# - session_expiry_refresh_window is a positive number
# 48 hours
"session_expiry": 172800,
# window to update the session window based on activity if
# nothing else has updated it (ie: refreshing tokens or userinfo)
#
# should be a positive number less than session_expiry
#
# For example, if session_expiry is set to 60 seconds and session_expiry_refresh_window value
# is set to 20 then activity in the last 20 seconds (40-60) of the window will 'slide' the window
# out session_expiry time from whenever the activity occurred
# 24 hours
"session_expiry_refresh_window": 86400,
# will re-use the same id (ie: same cookie) for a particular client if a session has expired
"session_retain_id": True,
# if the access token is expired and a refresh token is available, refresh
"refresh_access_token": True,
# fetch userinfo and include as X-Userinfo header to backing service
# only helpful if your specific provider has been implemented
"fetch_userinfo": True,
"introspect_access_token": False,
# which token (if any) to send back to the proxy as the Authorization Bearer value
# note the proxy must allow the token to be passed to the backend if desired
#
# possible values are access_token, or refresh_token
"authorization_token": "access_token"
},
"assertions": {
# assert the token(s) has not expired
"exp": True,
"nbf": True,
"iss": True,
# custom userinfo assertions
"userinfo": [],
"id_token": []
},
"cookie": {
"name": f"_{client.id}_session", # default is _oeas_oauth_session
# domain: "example.com", # defaults to request domain, could do sso with more generic domain
"path": "/"
# httpOnly: true,
# secure: false,
# sameSite: lax,
},
# xhr detection is determined by the presence of an 'origin' header OR X-Requested-With: XMLHttpRequest
"xhr": {
# defaults to 302 but could be set to anything
# if set to 401 the response will include a WWW-Authenticate header with proper realm/scopes
"redirect_http_code": 401,
# if set to true, the browser will be redirected to the referer
"use_referer_as_redirect_uri": True
},
"csrf_cookie": {
# TODO - this is disabled following CSRF mismatch problems
"enabled": False, # can disable the use of csrf cookies completely
# domain: "example.com", # defaults to request domain, could do sso with more generic domain
# path: "/",
# httpOnly: true,
# secure: false,
# sameSite: lax,
},
# see for details: https://github.com/travisghansen/external-auth-server/blob/master/HEADERS.md
"custom_error_headers": {},
"custom_service_headers": {
"X-Token": {
"source": "access_token",
"query_engine": "jp",
"query": "$",
"query_opts": {
"single_value": True
},
}
}
}],
},
"iat": datetime.utcnow()
}
Will this do.... or do you want me to fill-in some of the template options
I don't see how you could hit the block of code I have in mind with that config. The only other explanation is that you have different versions of the app running. What image tag are you using for the deployment? I'm wondering if it's been running for a while and you have some containers using an old latest and others using a newer latest.
Helm chart version: external-auth-server-0.1.0 The image: travisghansen/external-auth-server:latest ImageId: docker.io/travisghansen/external-auth-server@sha256:59afaf6672b380adfbeb22dfb399b0259d70cd7eb8d8f15c12945c19360d0407
It has indeed been running for a long time.... thought the last update we made to the chart was 2023-03-07 10:48:17.165190604 +0000 UTC deployed
That said, it could be that the container image has been cached in the cluster, and not been updated....
Now that i see that we are running using latest .... i think I'm gonna go ahead and pin the version to something specific
I'm guessing that's the root of the issue.
https://github.com/travisghansen/external-auth-server/blob/master/CHANGELOG.md#0130
bullet 4 use server-side storage of oauth / oidc state data
It's obviously a complex matrix of what tag you've picked, the pull policy, and the potential introduction of new nodes since that version was released 2023-1-22 but I'm relatively confident that is the issue.
I would redeploy with v0.13.0 and see if the issue persists.
Give me some hours then.... I'm gonna go through a release cycle and test
Okay.... So made a test. Upgraded the image to version: v0.13.0 and things seems to be working as it should, though now i have to see if i can manage to reproduce the issue.... hold tight and ill be back with more
So was going through the logs after the change on Thursday. I can see that the issue is still present, 503s are still being returned and i see the following in the logs.
TypeError: Cannot read properties of undefined (reading 'match')
at Object.parse (/home/eas/app/node_modules/uri-js/dist/es5/uri.all.js:874:29)
at OpenIdConnectPlugin.get_authorization_redirect_uri (/home/eas/app/src/plugin/oauth/index.js:2514:27)
at handle_auth_callback_request (/home/eas/app/src/plugin/oauth/index.js:1649:45)
at OpenIdConnectPlugin.verify (/home/eas/app/src/plugin/oauth/index.js:1924:29)
at runMicrotasks (<anonymous>)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async processPipeline (/home/eas/app/src/server.js:399:13)
Main thing I'm trying to understand now is how i can have a valid decodedStatePointer, that does not contain a state_id here:
https://github.com/travisghansen/external-auth-server/blob/master/src/plugin/oauth/index.js#L1912
Looking at the content of the decodedStatePointer from line https://github.com/travisghansen/external-auth-server/blob/master/src/plugin/oauth/index.js#L1916
It looks like i end up with the statePayload in the in the state query-parameter rather than the statePointerPayload
decocded state pointer: {
"request_uri":"https://<<domain>>.com/",
"aud": "216de************09b3",
"csrf":"c7b74**********0da0",
"req":{"headers":{}},
"request_is_xhr": false,
"iat": 1676881102
}
Though i cannot quite figure out why.
The only place I’ve seen that it’s possible to get that is if you use the logout features (which your tokens did not have enabled). Either that or a version mismatch from before the server-side state was implemented.
Right. So i have just wiped the sessions in key-cloak, all keys stored in redis and cycled the deployment of the EAS... Ill get back to you if the issue starts reappearing
So far so good... Nothing in the logs yet regarding the original issue....
However, I am noticing an similar error that is still causing the EAS to crash:

Now it seems that the state pointer contains the actual state ID, but that the state no longer exists when it tries to look it up in Redis. Not sure yet if its related to the wiping of Redis, though it did happen an hour after that was done.
I'll give it more time to see if that is persistent or not
It could be if you wiped redis. But state has a ttl on the redis entry of an hour or something….so if you take forever it won’t work in that scenario…which seems very unlikely unless someone walks away from their computer midway through the auth process.
Also note state is very ephemeral. As soon as auth comes back to eas we proactively wipe it out regardless of success or failure with the op.
Is this for a SPA?
It is indeed a SPA, All API requests has the 'X-Requested-With': 'XMLHttpRequest' header on them, in case you were wondering
It might be that someone simply stepped away from the computer in the middle of the auth flow.... Though that is indeed odd. Though did not expect that to crash the EAS
Yeah for sure we shouldn't crash, I'm pretty sure I have fix queued up for that already but will make sure it's in the next release no matter.
Do you have specific logic built into the SPA to handle api failures and redirect? In the case of a SPA there may be some strange behavior due to the concurrent nature of api requests etc but I'd have to think it through more thoroughly to say for sure :(