@@ -232,24 +232,50 @@ func (s *Server) updateOfflineSession(ctx context.Context, refresh *storage.Refr
232232 return nil
233233}
234234
235- // updateRefreshToken updates refresh token and offline session in the storage
235+ // updateRefreshToken updates refresh token and offline session in the storage.
236+ // Connector refresh is guarded by a per-refresh-ID mutex so only one concurrent
237+ // caller hits the IdP.
236238func (s * Server ) updateRefreshToken (ctx context.Context , rCtx * refreshContext ) (* internal.RefreshToken , connector.Identity , * refreshError ) {
237- var rerr * refreshError
238-
239239 newToken := & internal.RefreshToken {
240240 Token : rCtx .requestToken .Token ,
241241 RefreshId : rCtx .requestToken .RefreshId ,
242242 }
243-
244243 lastUsed := s .now ()
245244
245+ // Base identity
246246 ident := connector.Identity {
247247 UserID : rCtx .storageToken .Claims .UserID ,
248248 Username : rCtx .storageToken .Claims .Username ,
249249 PreferredUsername : rCtx .storageToken .Claims .PreferredUsername ,
250250 Email : rCtx .storageToken .Claims .Email ,
251251 EmailVerified : rCtx .storageToken .Claims .EmailVerified ,
252252 Groups : rCtx .storageToken .Claims .Groups ,
253+ ConnectorData : rCtx .connectorData ,
254+ }
255+
256+ rotationEnabled := s .refreshTokenPolicy .RotationEnabled ()
257+ reusingAllowed := s .refreshTokenPolicy .AllowedToReuse (rCtx .storageToken .LastUsed )
258+ needConnectorRefresh := rotationEnabled && ! reusingAllowed
259+
260+ if needConnectorRefresh {
261+ // Serialize concurrent refreshes for the same refresh ID.
262+ lock := s .getRefreshLock (rCtx .storageToken .ID )
263+ lock .Lock ()
264+ s .logger .Debug ("Acquired refresh lock" , "refreshID" , rCtx .storageToken .ID )
265+ defer func () {
266+ lock .Unlock ()
267+ s .logger .Debug ("Released refresh lock" , "refreshID" , rCtx .storageToken .ID )
268+ }()
269+
270+ // Double-check if another goroutine already refreshed while we waited:
271+ if ! s .refreshTokenPolicy .AllowedToReuse (rCtx .storageToken .LastUsed ) {
272+ s .logger .Debug ("Calling refreshWithConnector" , "ident" , ident )
273+ var rerr * refreshError
274+ ident , rerr = s .refreshWithConnector (ctx , rCtx , ident )
275+ if rerr != nil {
276+ return nil , ident , rerr
277+ }
278+ }
253279 }
254280
255281 refreshTokenUpdater := func (old storage.RefreshToken ) (storage.RefreshToken , error ) {
@@ -258,21 +284,16 @@ func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) (
258284
259285 switch {
260286 case ! rotationEnabled && reusingAllowed :
261- // If rotation is disabled and the offline session was updated not so long ago - skip further actions.
262287 old .ConnectorData = nil
263288 return old , nil
264289
265290 case rotationEnabled && reusingAllowed :
266291 if old .Token != rCtx .requestToken .Token && old .ObsoleteToken != rCtx .requestToken .Token {
267292 return old , errors .New ("refresh token claimed twice" )
268293 }
269-
270- // Return previously generated token for all requests with an obsolete tokens
271294 if old .ObsoleteToken == rCtx .requestToken .Token {
272295 newToken .Token = old .Token
273296 }
274-
275- // Do not update last used time for offline session if token is allowed to be reused
276297 lastUsed = old .LastUsed
277298 old .ConnectorData = nil
278299 return old , nil
@@ -281,29 +302,15 @@ func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) (
281302 if old .Token != rCtx .requestToken .Token {
282303 return old , errors .New ("refresh token claimed twice" )
283304 }
284-
285- // Issue new refresh token
286305 old .ObsoleteToken = old .Token
287306 newToken .Token = storage .NewID ()
288307 }
289308
290309 old .Token = newToken .Token
291310 old .LastUsed = lastUsed
292-
293- // ConnectorData has been moved to OfflineSession
294311 old .ConnectorData = nil
295312
296- // Call only once if there is a request which is not in the reuse interval.
297- // This is required to avoid multiple calls to the external IdP for concurrent requests.
298- // Dex will call the connector's Refresh method only once if request is not in reuse interval.
299- ident , rerr = s .refreshWithConnector (ctx , rCtx , ident )
300- if rerr != nil {
301- return old , rerr
302- }
303-
304- // Update the claims of the refresh token.
305- //
306- // UserID intentionally ignored for now.
313+ // Always update claims
307314 old .Claims .Username = ident .Username
308315 old .Claims .PreferredUsername = ident .PreferredUsername
309316 old .Claims .Email = ident .Email
@@ -313,15 +320,12 @@ func (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) (
313320 return old , nil
314321 }
315322
316- // Update refresh token in the storage.
317- err := s .storage .UpdateRefreshToken (ctx , rCtx .storageToken .ID , refreshTokenUpdater )
318- if err != nil {
323+ if err := s .storage .UpdateRefreshToken (ctx , rCtx .storageToken .ID , refreshTokenUpdater ); err != nil {
319324 s .logger .ErrorContext (ctx , "failed to update refresh token" , "err" , err )
320325 return nil , ident , newInternalServerError ()
321326 }
322327
323- rerr = s .updateOfflineSession (ctx , rCtx .storageToken , ident , lastUsed )
324- if rerr != nil {
328+ if rerr := s .updateOfflineSession (ctx , rCtx .storageToken , ident , lastUsed ); rerr != nil {
325329 return nil , ident , rerr
326330 }
327331
0 commit comments