nats-io/nats-server · error
jwt subject nkey and provided nkey do not match
Error message
jwt subject nkey and provided nkey do not match
What it means
During saveIfNewer, after decoding the existing and new JWTs, the store checks that the new JWT's subject claim matches the public key the caller supplied as the storage key. A mismatch means the caller is trying to file a JWT under a key that does not own it, which would corrupt the resolver's key-to-JWT mapping.
Source
Thrown at server/dirstore.go:535
dirPath := filepath.Dir(path)
if _, err := validateDirPath(dirPath); err != nil {
if err := os.MkdirAll(dirPath, defaultDirPerms); err != nil {
return err
}
}
if _, err := os.Stat(path); err == nil {
if newJWT, err := jwt.DecodeGeneric(theJWT); err != nil {
return err
} else if existing, err := os.ReadFile(path); err != nil {
return err
} else if existingJWT, err := jwt.DecodeGeneric(string(existing)); err != nil {
// skip if it can't be decoded
} else if existingJWT.ID == newJWT.ID {
return nil
} else if existingJWT.IssuedAt > newJWT.IssuedAt {
return nil
} else if newJWT.Subject != publicKey {
return fmt.Errorf("jwt subject nkey and provided nkey do not match")
} else if existingJWT.Subject != newJWT.Subject {
return fmt.Errorf("subject of existing and new jwt do not match")
}
}
store.Lock()
cb := store.changed
changed, err := store.write(path, publicKey, theJWT)
store.Unlock()
if err != nil {
return err
} else if changed && cb != nil {
cb(publicKey)
}
return nil
}
func xorAssign(lVal *[sha256.Size]byte, rVal [sha256.Size]byte) {
for i := range rVal {View on GitHub (pinned to 3a66a489d2)
Solutions
- Ensure the key passed to Store/saveIfNewer equals newJWT.Subject as decoded from the JWT
- Decode the JWT first and use its claims.Subject as the storage key
- Audit Merge callers that zip external key lists with JWT lists for off-by-one/mispairing
Example fix
// before store.Store(issuerKey, accJWT) // error: jwt subject nkey and provided nkey do not match // after claims, _ := jwt.DecodeAccountClaims(accJWT) store.Store(claims.Subject, accJWT)
Defensive patterns
Strategy: validation
Validate before calling
claims, err := jwt.DecodeAccountClaims(theJWT)
if err != nil { return err }
if claims.Subject != publicKey { return fmt.Errorf("key %q does not own jwt subject %q", publicKey, claims.Subject) }
err = store.Store(publicKey, theJWT) Try / catch
if err := store.Store(pub, jwt); err != nil && strings.Contains(err.Error(), "do not match") {
log.Printf("jwt/key mismatch for %s", pub)
} Prevention
- Always use claims.Subject as the storage key
- Pair keys and JWTs carefully in bulk merge code
- Add unit tests covering key/subject agreement
When it happens
Trigger: Calling Store/Merge where publicKey != the Subject claim of theJWT (after the existing-JWT short-circuits for identical ID or older IssuedAt).
Common situations: Passing an issuer key instead of the subject key; storing activation JWTs under the account key rather than the correct subject; bug in batch-merge code pairing keys with the wrong JWTs.
Related errors
- malformed request
- validation errors: %v
- authorization response had validation errors: %v
- subject of existing and new jwt do not match
- got corrupted escaped character
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/e321bbce5abdb5f5.
Report an issue: GitHub.