-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: 1.17.6, 2.9.2
-
Component/s: Authentication
-
None
-
None
-
Go Drivers
-
None
-
None
-
None
-
None
-
None
-
None
With authMechanism=MONGODB-OIDC and ENVIRONMENT:gcp, applications occasionally get ReauthenticationRequired (391) from Cursor.Next / Cursor.All when the access token expires while a cursor is open. The initial find/aggregate recovers from 391, but the following getMore does not, so the error reaches the application. Calling Next again cannot recover the cursor.
Expected: per the auth specification ("Reauthentication"), the driver reauthenticates and re-attempts the getMore, irrespective of retryability.
Cause (from the driver source):
- x/mongo/driver/operation.go handles 391 only if op.Authenticator != nil (v2.9.2 line 955, v1.17.6 line 920).
- x/mongo/driver/batch_cursor.go builds the getMore and killCursors operations without an Authenticator, and CursorOptions has no field to carry it (v2.9.2 line 477, v1.17.6 line 429). mongo/collection.go sets Authenticator only on the initial command.
- x/mongo/driver/auth/oidc.go caches the GCP token without an expiry (ExpiresAt is nil and unused) and refreshes it only after a 391 or a failed handshake, so all pooled connections expire together and cursors spanning that moment fail.
Affected: v1.17.6, v2.4.1, v2.8.0, v2.9.2, and master as of 2026-10-08.
Reproduction: open a cursor with a batchSize small enough to need several getMore calls, let the OIDC token expire (or have the server return 391 on getMore), then iterate. Cursor.Err() returns (ReauthenticationRequired).
Suggested fix: add an Authenticator to CursorOptions / BatchCursor, set it in the cursor-returning operations' Result (find, aggregate, listCollections, listIndexes), and pass it to the getMore and killCursors operations.
Environment:
Go 1.27.1 (linux/amd64 containers on GKE; repro run on darwin/arm64)
Driver: go.mongodb.org/mongo-driver/v2 v2.8.0 in production; reproduced with v2.9.2. Also affects go.mongodb.org/mongo-driver v1.17.6.
Server: MongoDB Atlas, dedicated replica set on GCP, MongoDB 8.0.34. Repro verified against a local mongod 8.0.32.
Auth: MONGODB-OIDC, ENVIRONMENT:gcp with TOKEN_RESOURCE (GKE Workload Identity).
Repro without waiting for token expiry: a failCommand failpoint returns 391 once. Run against a mongod started with --auth and --setParameter enableTestCommands=1. With SCRAM the reauth itself cannot succeed, but the output shows that the driver attempts reauthentication for find and does not attempt it for getMore.
Output (driver v2.9.2, mongod 8.0.32):
391 on find: error reauthenticating: DefaultAuthenticator does not support reauthentication
391 on getMore: (ReauthenticationRequired) Failing command via 'failCommand' failpoint (after 2 of 10 documents)
// Repro: the driver attempts reauthentication when find returns 391, but not when getMore does. // Run against a mongod started with --auth and --setParameter enableTestCommands=1, for example: // // MONGODB_URI='mongodb://admin:admin@localhost:27017/?authSource=admin' go run . package main import ( "context" "fmt" "log" "os" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) func main() { ctx := context.Background() client, err := mongo.Connect(options.Client().ApplyURI(os.Getenv("MONGODB_URI"))) if err != nil { log.Fatal(err) } defer func() { _ = client.Disconnect(ctx) }() coll := client.Database("repro").Collection("docs") _ = coll.Drop(ctx) docs := make([]any, 10) for i := range docs { docs[i] = bson.D{{Key: "i", Value: i}} } if _, err := coll.InsertMany(ctx, docs); err != nil { log.Fatal(err) } failNext(ctx, client, "find") _, err = coll.Find(ctx, bson.D{}) fmt.Println("391 on find: ", err) failNext(ctx, client, "getMore") cursor, err := coll.Find(ctx, bson.D{}, options.Find().SetBatchSize(2)) if err != nil { log.Fatal(err) } n := 0 for cursor.Next(ctx) { n++ } fmt.Printf("391 on getMore: %v (after %d of %d documents)\n", cursor.Err(), n, len(docs)) } // failNext makes the server answer the next cmd with ReauthenticationRequired (391). func failNext(ctx context.Context, client *mongo.Client, cmd string) { err := client.Database("admin").RunCommand(ctx, bson.D{ {Key: "configureFailPoint", Value: "failCommand"}, {Key: "mode", Value: bson.D{{Key: "times", Value: 1}}}, {Key: "data", Value: bson.D{ {Key: "failCommands", Value: bson.A{cmd}}, {Key: "errorCode", Value: 391}, }}, }).Err() if err != nil { log.Fatal(err) } }
- related to
-
GODRIVER-3572 Add a prose test for OIDC reauthentication when a session is involved
-
- Backlog
-
-
DRIVERS-3195 Add a prose test for OIDC reauthentication when a session is involved
-
- Implementing
-