hashicorp/nomad · error

nonce reuse detected

Error message

nonce reuse detected

What it means

ErrNonceReuse is returned when the OIDC request cache already holds an entry for the same client nonce. Nonces must be unique per auth-url request; a collision indicates a bug or forged/duplicated callback traffic, so the store refuses the request.

Source

Thrown at lib/auth/oidc/request.go:17

// Copyright IBM Corp. 2015, 2026
// SPDX-License-Identifier: BUSL-1.1

package oidc

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/hashicorp/cap/oidc"
	"github.com/hashicorp/golang-lru/v2/expirable"
)

var (
	ErrNonceReuse = errors.New("nonce reuse detected")
	// ErrTooManyRequests is returned if the request cache is full.
	// Realistically, we expect this only to happen if the auth-url
	// API endpoint is being DOS'd.
	ErrTooManyRequests = errors.New("too many auth requests")
)

// MaxRequests is how many requests are allowed to be stored at a time.
// It needs to be large enough for legitimate user traffic, but small enough
// to prevent a DOS from eating up server memory.
const MaxRequests = 1000

// NewRequestCache creates a cache for OIDC requests.
// The JWT expiration time in the cap library is 5 minutes,
// so timeout should be around that long.
func NewRequestCache(timeout time.Duration) *RequestCache {
	return &RequestCache{
		c: expirable.NewLRU[string, *oidc.Req](MaxRequests, nil, timeout),
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Generate a fresh unique nonce for every auth-url request on the client side and retry.
  2. Investigate the client generating the duplicate nonce — nonce generation should use a CSPRNG per request.
  3. If triggered by a replayed callback, treat it as suspicious traffic and drop it rather than retrying.

Example fix

// before
req, _ := oidc.NewReq(provider, clientID, clientSecret, oidc.WithNonce(fixedNonce))
rc.store(req) // second store of same nonce fails
// after
nonce, _ := uuid.GenerateUUID()
req, _ := oidc.NewReq(provider, clientID, clientSecret, oidc.WithNonce(nonce))
rc.store(req)
Defensive patterns

Strategy: try-catch

Validate before calling

if rc.Has(req.Nonce()) {
    req = regenerateWithNewNonce(req)
}

Try / catch

err := rc.store(req)
if errors.Is(err, oidc.ErrNonceReuse) {
    // regenerate nonce and retry once, or reject the callback as replay
    return fmt.Errorf("rejecting duplicate OIDC nonce: %w", err)
}

Prevention

When it happens

Trigger: RequestCache.store (storeLocked) is called twice with an oidc.Req whose Nonce() is already present in the LRU cache — checked at request.go:58 via rc.c.Get(req.Nonce()).

Common situations: A client replaying the same auth-url request; two browser tabs racing with an identical nonce; a broken client that regenerates the same nonce; tests deliberately re-storing the same req (request_test.go:26).

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/bd807c32ebe5f204. Report an issue: GitHub.