Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(UserManager): handle concurrent token refresh requests via leader election #434

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

DASPRiD
Copy link
Contributor

@DASPRiD DASPRiD commented Mar 22, 2022

Introduces leader election for concurrent token refresh requests. This adds a new dependency (broadcast-channel) which takes care of the election process.

Closes #430

@DASPRiD DASPRiD changed the title fix(UserManager): handle concurrent token refresh requests via leader… fix(UserManager): handle concurrent token refresh requests via leader election Mar 22, 2022
@codecov
Copy link

codecov bot commented Mar 22, 2022

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 75.40%. Comparing base (c9fcaa0) to head (f760441).
Report is 1100 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #434      +/-   ##
==========================================
+ Coverage   75.25%   75.40%   +0.15%     
==========================================
  Files          44       44              
  Lines        1572     1590      +18     
  Branches      288      292       +4     
==========================================
+ Hits         1183     1199      +16     
- Misses        363      364       +1     
- Partials       26       27       +1     
Flag Coverage Δ
unittests 75.40% <100.00%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@pamapa
Copy link
Member

pamapa commented Mar 23, 2022

Great work , thanks for the fast response!

Do we really need the broadcast-channel library, modern browsers support the broadcast-channel feature natively. When i look into https://developer.mozilla.org/en-US/docs/Web/API/Broadcast_Channel_API:

  • IE has no support -> OK
  • messageerror event is not all over supported -> we do not need it -> OK

The leader election feature could be taken, simplified and converted into TypeScript from the broadcast-channel library.

The main idea behind this is to have as few external dependencies as really needed....

@pamapa pamapa requested review from kherock and pamapa March 23, 2022 07:56
@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

I suppose it should be no problem to use the native broadcast-channel. I'll have a look at this.

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

@pamapa Just wondering, as this would ease implementation a lot: Is the browser support for the LockManager API too thin/new to be used?

https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API

@pamapa
Copy link
Member

pamapa commented Mar 23, 2022

Good question. We only care about modern browsers (primary chrome, firefox, edge, safari + mobile versions, not IE) anyway.

In that list i only see Safari to be a problem for us now. Maybe we could first detect if that API exists and then use it, if not found we keep current way, which was the default for many years in the predecessor library...

@kherock What is your opinion here?

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

That sounds like a sane way to me to implement it!

@kherock
Copy link
Collaborator

kherock commented Mar 23, 2022

Web locks seem like a more appropriate API to use since we don't have any information to publish between tabs.

Also, I have concerns over using the _userStoreKey as the resource to lock onto. It seems like it we should only lock when the web storage backend is shared across tabs, since normally it isn't when sesssionStorage is being used. Today, if I open up a new tab and assume the same identity with a silent refresh, the new tab will have its own unique refresh token and access token in sessionStorage. By always locking onto _userStoreKey, we'd be unnecessarily holding up the other tabs with matching client IDs. The resource string should be derived from the refresh token itself, or locking should be conditional depending on the way storage persists across tabs.

Are you replacing the webstorage backend with localStorage @DASPRiD? If so, I think a better way to do this would be something like this

// extend with a method for obtaining a lock and running a task
interface StateStore {
  async runTask(key: string, task: () => Promise<void>);
}

class WebStorageStateStore implements StateStore {
  /** whether tasks run against this store require synchronization from other instances of oidc-client */
  private _sync: boolean;

  public constructor({ prefix = "oidc.", store = localStorage, sync } = {}) {
        this._store = store;
        this._prefix = prefix;
        this._sync = sync ?? store === localStorage
  }

  public async runTask(key: string, task: () => void | Promise<void>) {
    if (!this._sync) return await task();
    await navigator.locks.request(this._prefix + key, async lock => {
      // The lock has been acquired.
      await task();
      // Now the lock will be released.
    });
  }
}

This locking utility probably doesn't need to be in WebStorageStateStore, but in my mind, the storage backend should influence its default behavior regardless of where it is.

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

I am using localStorage, yes. But Wouldn't sessionStorage suffer from the same issue, two tabs still access the same sessionStorage value with the same key. Only memory storage wouldn't suffer from this issue.

I agree that we could limit this to the refresh token function and locking onto the refresh token. I assume we wouldn't need a BroadcastChannel in this case, as after acquiring the lock, we could potentially just verify if the access token was just refreshed recently (e.g. in the last X seconds).

@kherock
Copy link
Collaborator

kherock commented Mar 23, 2022

Page sessions are unique per tab! From MDN.

Whenever a document is loaded in a particular tab in the browser, a unique page session gets created and assigned to that particular tab. That page session is valid only for that particular tab.

In my case, I'm writing an enterprise application where sessions don't persist beyond the lifespan of a tab. So for me separate tabs for the same application shouldn't have to ever interact or conflict with each other since they never shared any state to begin with. But it does seem like there is an exception where a browser may duplicate a tab and its associated session storage:

Duplicating a tab copies the tab's sessionStorage into the new tab.

So it does make sense that we need to provide some synchronization mechanism independent of the storage backend.

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

Uh, so this becomes kinda tricky. Basically when you duplicate a tab using session storage while using refresh tokens, they become permanently linked to each other.

I guess the following method could be implemented (given that LockManager is available, otherwise we don't handle it):

  1. Acquire a lock on the refresh token.
  2. When lock is acquired, perform the token refresh and broadcast the new user object via BroadcastChannel.
  3. When a user is received via the BroadcastChannel while waiting for a lock, cancel the lock request.
  4. When a user is received via the BroadcastChannel outside of acquiring a lock, update the local user and reset the refresh timer.

Does this sound right to you?

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 23, 2022

Well, I went ahead and implemented this to solve it for localStorage, which was relatively easy. I still have to think how exactly I want to handle that for sessionStorage. Any input is welcome!

@pamapa
Copy link
Member

pamapa commented Mar 24, 2022

Thanks for working on this, looks much simpler now with locking. For easier review could you please squash your changes into a single commit?

@pamapa pamapa marked this pull request as draft March 24, 2022 07:31
@Badisi
Copy link
Contributor

Badisi commented Mar 24, 2022

@DASPRiD thanks for raising this issue as I also faced it a few months ago and was yet to find an ideal solution.


In my case, I'm using a simple web app which stores its tokens in memory (no local/session storage of any sort).

Opening this same web app in two different tabs, results in the following:

Using [email protected]

Revoke Refresh Token: ON (ie. activating refresh token rotation)

When ON, Keycloak revokes refresh tokens and issues another token that the client must use. This action applies to OIDC clients performing the refresh token flow.

  1. App is opened in TabA and receives a Refresh Token (RTA1)
  2. App is opened in TabB and receives RTB1
  3. TabA is about to expire and request a renew
    • RTA1 is used and revoked, and RTA2 is given in exchange
  4. TabB is about to expire and request a renew
    • RTB1 is used but as TabA already did a rotation, TabB is perceived as a potential threat and all outstanding tokens are invalidated
    • Response from oidc-client is Error: Stale token
    • TabB now requires a full login
  5. TabA is about to expire again and request a renew
    • RTA2 is used but it was revoked during 4) so TabA is also blocked and requires a full login

Using Auth0

Refresh Token Rotation: ON

When enabled, as a result of exchanging a refresh token, a new refresh token will be issued and the existing token will be invalidated. This allows for automatic detection of token reuse if the token is leaked. In addition, an absolute expiration lifetime must be set.

No error whatsoever, both tabs can renew their tokens.

Conclusion

I'm not familiar enough with the OIDC spec so I don't know if this could relates to a bug in one or another.
But I think this PR could solve the first case with Keycloak, if only the Refresh Token is not used as the resource to lock on (because both tabs receive different refresh tokens and thus the BroadcastChannel wouldn't work).

@kherock
Copy link
Collaborator

kherock commented Mar 24, 2022

@Badisi thanks for sharing this, I was also seeing a similar error with my Keycloak application and decided to simply disable refresh tokens, but your explanation makes sense.

Rather than locking on a refresh token, what happens if we lock onto session_state instead? And then subscribe to broadcast channels using that state as the topic name?

@Badisi
Copy link
Contributor

Badisi commented Mar 24, 2022

That would work because session_state is unique and thus identical for each tabs.
But it would work only for Keycloak (at least) because there is no such info returned by Auth0...

The only common data I'm thinking of is the client-id.

@Badisi
Copy link
Contributor

Badisi commented Mar 24, 2022

The more I think about it, the more I realize it should be an issue on Keycloak side.

Auth0 reasoning (see Automatic-Reuse-Detection) seems way more logical.
They invalidates the RT family only if an already invalided RT is being reused.

Whereas Keycloak is invalidating the RT family if any previously delivered RT (before the rotation) is being reused.
Which makes no sense to me...

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 24, 2022

Whereas Keycloak is invalidating the RT family if any previously delivered RT (before the rotation) is being reused. Which makes no sense to me...

Yeah, this seems to be a Keycloak issue. The way their implementation works, you could never have two independent clients authenticated at the same time, because they invalidate each others tokens. The way auth0 works makes a lot more sense.

@DASPRiD DASPRiD force-pushed the bugfix/handle-concurrent-token-refresh branch from 4ac6086 to b9d5c01 Compare March 25, 2022 16:31
@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 25, 2022

Thanks for working on this, looks much simpler now with locking. For easier review could you please squash your changes into a single commit?

I just squashed it all down to a single commit. Regarding linked sessionStorage sessions, I think we should consider that a different issue.

package-lock.json Outdated Show resolved Hide resolved
@pamapa pamapa added the bug Something isn't working label Mar 25, 2022
@DASPRiD DASPRiD force-pushed the bugfix/handle-concurrent-token-refresh branch from b9d5c01 to 14436d1 Compare March 25, 2022 16:43
@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 25, 2022

For some reason, I cannot answer to your review comments:

Yes, the check for navigator.locks covers testing for the existence of the BroadcastChannel feature, as the latter is supported much longer already.

@DASPRiD DASPRiD marked this pull request as ready for review March 25, 2022 16:45
@DASPRiD DASPRiD requested a review from pamapa March 25, 2022 16:49
… election

Introduces leader election for concurrent token refresh requests. Gracefully
falls back when Web Lock API is not available.

Closes authts#430
@DASPRiD DASPRiD force-pushed the bugfix/handle-concurrent-token-refresh branch from 14436d1 to fb75619 Compare March 25, 2022 18:49
@pamapa
Copy link
Member

pamapa commented Mar 30, 2022

@Badisi Thanks for helping reviewing. Valid concerns, which must be answered/resolved before we merge...

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Mar 31, 2022

@pamapa are we waiting for @kherock to shime in here?

@pamapa
Copy link
Member

pamapa commented Apr 1, 2022

@DASPRiD Yes we are waiting for @kherock for his approval and please also answer to @Badisi questions...

@pamapa pamapa self-requested a review April 1, 2022 06:33
@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 1, 2022

@pamapa I thought I answered all his questions :)

@pamapa
Copy link
Member

pamapa commented Apr 1, 2022

I can not see the answers to #434 (comment) + #434 (comment)

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 4, 2022

Oh, stupid me, I had a review running instead of commenting directly, so my comments were pending :D

The second one is not really a question to me, is it? Technically @kherock already said that we should lock on the refresh token, if you guys want a slightly different naming though, we can change that.

@Badisi
Copy link
Contributor

Badisi commented Apr 4, 2022

@DASPRiD , as I explained here, using the refresh token as a lock key would be useless because each browser tabs receives a different refresh token.

To me the only data that is commonly shared by each tabs is the client-id.
Just waiting on @kherock thoughts about that 🙂.

Comment on lines +292 to +295
if (!user) {
user = await refreshUser();
broadcastChannel.postMessage(user);
}
Copy link
Collaborator

@kherock kherock Apr 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way that this code handles competing requests seems leaky. It looks like when two tabs both ask for a lock on the refresh token state, they can still both send refresh tokens sequentially. It seems to make an assumption that sending a message informing the other tab of the new user via the broadcast channel is a synchronous operation, but if this were the case, no web locks would be necessary at all. I doubt that there will ever be a case where releasing and re-obtaining the lock takes longer than sending a BroadcastChannel message, but I think the possibility is there.

Instead, I think it would be better to use the Web Locks API to perform leader election:

const topic = `refresh_token_${state.refresh_token}`;
const broadcastChannel = new BroadcastChannel(topic);
const bcUserPromise = new Promise(resolve => broadcastChannel.addEventListener(ev => resolve(ev.data)));

const user = await navigator.locks.request(topic, { ifAvailable: false }, async (lock) => {
  // a null `lock` implies that this tab lost in leader election
  if (lock) {
    const user = await refreshUser();
    await this.storeUser(user);
    broadcastChannel.postMessage(user);
    return user;
  } else {
    return await bcUserPromise;
  }
});
broadcastChannel.close();
this._events.load(user);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this implementation best.

@kherock, just a few questions:

  1. Why is storeUser(user) only performed by the leader ? (and not by everyone at the end, like _events.load)
  2. Is a timeout necessary in case postMessage never occurred ? (ex: refreshUser() taking too long and in the meantime user closes the leader tab)
  3. Still wondering about the proper lock key to use... 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't think about the scenario of the tab being interrupted, but more realistically, my code doesn't handle when the leader encounters an error either.

storeUser writes the user to localStorage/sessionStorage which is presumably shared by both tabs in this scenario, so it only needs to be serialized and stored once. The user actually doesn't even need to be sent over the channel at all, the other tab could just as well do return await this._loadUser() after receiving the notification from the leader (or by waiting for the lock to release, though this doesn't account for the lock releasing because of a failure or interruption).

I think think finding a "proper" lock key to use in a separate issue. My hunch is that we should lock onto session_state when the IDP implements OIDC sessions (e.g. Keycloak) and fall back to the refresh token.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Agreed that some error handling needs to be made.

  • which is presumably shared by both tabs in this scenario

    If I'm not mistaken, sessionStorage is not shared between tabs - so it won't work in that case.

  • I've seen session_state being returned by Keycloak (but not Auth0), so yes we could use it if we have it or fall back otherwise. But I'm still not confident in using refresh token as the fall back. Each tab receives a different refresh token, which makes the purpose of the lock completely useless.

Copy link
Collaborator

@kherock kherock Apr 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In instances where sessionStorage isn't shared, how would two tabs obtain the same refresh token in the first place? Also, see this comment: #434 (comment)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DASPRiD, I'm using neither LocalStorage nor SessionStorage, I'm keeping everything in memory. But as I see it, this concurrency issue exists no matter the type of storage, so I would opt for a way to solve it once and for all (and not only for LocalStorage). Why do you think your proposal does only cover LocalStorage ? I was convinced that simply using a common lock key would be enough to solved it for SessionStorage/MemoryStorage too. Don't you think ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually no, this issue does not affect memory storage, as memory storage is always bound to it's own session without sharing it with any other process.

The problem with SessionStorage is the part about session storage duplication when a tab is duplicated. In that instance, they will share the same session (from the perspective of the IDP), but not from their own perspective.

Copy link
Contributor Author

@DASPRiD DASPRiD Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kherock Actually, I do see an issue with using client ID + sub as the lock key: It would block unrelated tabs with different sessions but same client ID + sub from performing their refresh, that's why I was using the refresh token.

Copy link
Contributor

@Badisi Badisi Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then we are talking about two different concurrency issues.

  • The one you are facing, ie. when tokens are physically shared between tabs (over a storage). Tabs should wait for the first one to refresh it for the others.
  • And the one I'm facing (only seen with Keycloak and might be a bug but didn't get any answers from them yet), where the first tab to do a refresh will cancel any previous generated tokens for that same client_id/session (ie. the ones generated from the other tabs). So here, tabs should also wait for the first one to refresh the tokens and send them to the others. This feels hacky and weird (because they all started with different tokens and ends with identical ones) but at least your PR could solve it, as long as the lock key is identical for each tabs 🙂.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well yeah, the issue is absolutely on their side. Solving it with a shared lock key like that is a hacky way to solve your issue though. Especially since it would only really solve it as long as your are using local storage. If you are using anything else (based on Kheros asked change of not using the BroadcastChannel), this would not help with your issue.

It would, as mentioned above, also introduce the issue that it would block possibly unrelated tabs from performing their refresh. I really only see one way here to address your issue without affecting all other users:

  1. You would have to opt into local storage (since Keycloak forces a shared session on you anyway).
  2. Add an option to the Usermanager so you can opt into using client ID + sub as the lock key, although the default would be the refresh token.

@kherock Actually, instead of using the refresh token, we could also add a randomly generated ID to the store which is used as common identifier for the lock, but when session_state is available, store that as the common identifier. That way it would address @Badisi issue, and make us not rely on the value of the refresh token.

Although to ultimately address their issue, we'd need to use the BroadcastChannel. Thinking about that, we could add a delay of maybe 250ms or so into the leader function. That would address any possible common race condition. We probably might not even need such a long delay.

src/UserManager.ts Show resolved Hide resolved
@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 17, 2022

@kherock Can you have a look at the inline conversation? :)

@Badisi
Copy link
Contributor

Badisi commented Apr 20, 2022

It might help summarizing a bit because this discussion is quite long :)
(so correct me if I'm wrong or if something is missing)

Pending required changes:
- [ ] Change the name of the key for the BroadcastChannel and the Lock
=> use session_state when available or refresh_token as a fallback
- [ ] Eliminate potential race conditions
=> @kherock proposal here
OR
=> @DASPRiD proposal : "use a 250ms delay in the leader function"

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 20, 2022

Thanks for the summary. I have to add a little bit to that, so let me see if I can summarize that in a better way, at least from my point of view :)

  • Instead of relying on the refresh_token as shared identifier, use either session_state from the IDP, or use a randomly generated ID as fallback if session_state is not available.

    This would not address SessionStorage or MemoryStorage issues with a shared state, as that requires syncing with a potentially unknown shared key.

  • Elimiate race conditions. Kherock's proposal is insufficient, as it introduces an issue when the selected leader dies. Relying on the storage to figure out if another leader performed the refresh is not ideal either, as that relies on LocalStorage being used.

    Personal recommendation is to use a short delay in the leader function (something in the lower ms range should be sufficient.

@Badisi
Copy link
Contributor

Badisi commented Apr 21, 2022

@DASPRiD, thanks for the clarifications !
@kherock can you validate those points so he can move forward with the changes ? :)

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 21, 2022

One last thing though; I had a small epiphany yesterday when going to bed:

We could actually solve all problems by moving the leader election up to a higher level, so that for each session (determined by the session state or the randomly generated state key), only one tab is at all responsible for handling any refresh logic, that includes watching for the token to expire. All non-leaders would simply be informed through the BroadcastChannel.

This would solve the session duplication issue, as well as the problem with key cloak of having all clients share the same session, independent of their storage mechanism. It would also solve the race condition problem, as a leader would only die when a tab is actually closed, not every time a refresh happened. There'd still be a tiny chance that the user closes a tab exactly in the moment when the leader tries to perform a refresh, but a small delay at the start of the leader election would solve that.

Any thoughts about that?

@kherock
Copy link
Collaborator

kherock commented Apr 25, 2022

Hi, sorry for the long delay.

I do like the concept of moving leader election up to a higher level. I didn't see a good explanation of how a new leader is elected when the current one dies (I don't think we can rely on the leader's BroadcastChannel message making it out as it's dying).

What if, upon starting the SilentRenewService, a WebLock promise is created and BroadcastChannel listeners are set up. The topic used is the session state, falling back to a refresh token. The leader who obtains the lock first will hold it for the duration of its existence. The next competing instance will take over as soon as the leader dies.

Upon being granted the lock, the instance checks that the lock's topic still matches the current the session state or refresh token; if the value doesn't match, it means that the current leader has already changed topics after storing the state. In this case, the instance will start over and attempt to obtain a new lock on the updated value.

Once the leader has confirmed the lock, it sets up the expiration event listeners and unregisters its BroadcastChannel listeners. In order for the lock to stay active, it should return a Promise that only resolves when the session state or refresh token changes.

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Apr 28, 2022

With slight modifications, that sounds like a good idea to me. What's kinda annoying is that we still have to treat the case where WebLocks aren't available. That makes the implementation more messy than it really would need to be.

I'm a little busy with another project at the moment though, but I'll come back to this ASAP. I might create a new PR though, as the diff will be wildly different I assume.

@pamapa pamapa marked this pull request as draft April 29, 2022 06:39
@willykurmann
Copy link

Hello @DASPRiD,

When do you think you'll be able to merge this PR and release a new package ?
I'm really looking forward to it !

Thanks for the job 👍

@weyert
Copy link

weyert commented Nov 17, 2022

I am pretty interested in this feature. I was wondering how we could progress this PR? Is there anyway I could help with this?

What's kinda annoying is that we still have to treat the case where WebLocks aren't available. That makes the implementation more messy than it really would need to be.

I have been trying to use the Weblock API inside an IFRAME and appears to work. Only not 100% sure if that's due some configured CORS policy or not.

In which case would Lock API not available? Appears to be well supported by the modern browsers. It's not working when there is not a secure context but for an authentication library I can't imagine a case were we wouldn't have this.

@adamtang79
Copy link

Hi. When will this commits pulled to the main branch or new release?

@pamapa
Copy link
Member

pamapa commented Feb 27, 2023

@adamtang79 this merge request is not ready and needs a rework, currently it looks stalled.
@DASPRiD Do you still use this library and do you have time to finish this feature?

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Feb 27, 2023

@pamapa Sorry, I moved on a while ago to another library and don't really have to time to look further into this.

@adamtang79
Copy link

@pamapa Sorry, I moved on a while ago to another library and don't really have to time to look further into this.

Hi @DASPRiD. It's okay. Mind to tell me which library you have settled on? Just to learn more. Thanks :)

@DASPRiD
Copy link
Contributor Author

DASPRiD commented Feb 27, 2023

@pamapa Sorry, I moved on a while ago to another library and don't really have to time to look further into this.

Hi @DASPRiD. It's okay. Mind to tell me which library you have settled on? Just to learn more. Thanks :)

I ended up using @axa-fr/react-oidc (there's also a vanilla package).

@weyert
Copy link

weyert commented Feb 27, 2023

In what manner does it need to be reworked?

@DASPRiD At first sight that library doesn't appear to have a similar leader election functionality as suggested through this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Refresh Token issue with multiple tabs open
7 participants