Repository: nats-io/nats.go (go)

Issue:
Add UserCredentialBytes() in addition to UserCredentials()
### Proposed change

```golang
// UserCredentialBytes is a convenience function that takes a file bytes
// for a user's JWT and a filename for the user's private Nkey seed.
func UserCredentialBytes(userOrChainedFileBytes []byte, seedFiles ...[]byte) Option {
	userCB := func() (string, error) {
		return userFromBytes(userOrChainedFileBytes)
	}
	var keyFileBytes []byte
	if len(seedFiles) > 0 {
		keyFileBytes = seedFiles[0]
	} else {
		keyFileBytes = userOrChainedFileBytes
	}
	sigCB := func(nonce []byte) ([]byte, error) {
		return sigBytesHandler(nonce, keyFileBytes)
	}
	return UserJWT(userCB, sigCB)
}
```

```golang
func userFromBytes(userFileBytes []byte) (string, error) {
	if len(userFileBytes) == 0 {
		return _EMPTY_, fmt.Errorf("nats: user file is empty")
	}
	defer wipeSlice(userFileBytes)
	return nkeys.ParseDecoratedJWT(userFileBytes)
}
```

```golang
// Sign authentication challenges from the server.
// Do not keep private seed in memory.
func sigBytesHandler(nonce []byte, seedFileBytes []byte) ([]byte, error) {
	kp, err := nkeyPairFromSeedFileBytes(seedFileBytes)
	if err != nil {
		return nil, fmt.Errorf("unable to extract key pair from file: %w", err)
	}
	// Wipe our key on exit.
	defer kp.Wipe()

	sig, _ := kp.Sign(nonce)
	return sig, nil
}
```

### Use case

Hello everyone!
We've started using the Synadia cloud these days, and experiencing configuration discomfort.
What I'm talking about:
It's kinda inconvenient to mount the credentials file into the destination pod - it's much easier to pass file content via OS environment variables (following The Twelve-Factor App principles) and pass that data into the NATS connect function like this:

```golang
    nc, err := nats.Connect(natsUrl, nats.UserCredentialBytes(credentialsFileBytes), nats.Name(serviceName))
```

So I suggest adding several functions to achieve that.
What do you think - does this make sense?

### Contribution

_No response_

Pull request:
[ADDED] UserCredentialBytes() option
Fixes #1873

Allows passing JWT and seed credentials as byte slices instead of file paths.
Mirrors UserCredentials() but accepts bytes directly. Supports both separate 
JWT/seed bytes and chained credential format.

Co-authored-by: @AsyncLegs

Patch:
diff --git a/nats.go b/nats.go
index 20833b5..f18e32b 100644
--- a/nats.go
+++ b/nats.go
@@ -1247,6 +1247,35 @@ func UserCredentials(userOrChainedFile string, seedFiles ...string) Option {
 	return UserJWT(userCB, sigCB)
 }
 
+// UserCredentialBytes is a convenience function that takes the JWT and seed
+// values as byte slices. This allows passing credentials directly from memory
+// or environment variables without needing to write them to disk.
+func UserCredentialBytes(userOrChainedFileBytes []byte, seedFiles ...[]byte) Option {
+	userCB := func() (string, error) {
+		return nkeys.ParseDecoratedJWT(userOrChainedFileBytes)
+	}
+
+	var seedBytes []byte
+	if len(seedFiles) > 0 {
+		seedBytes = seedFiles[0]
+	} else {
+		seedBytes = userOrChainedFileBytes
+	}
+
+	sigCB := func(nonce []byte) ([]byte, error) {
+		kp, err := nkeys.ParseDecoratedNKey(seedBytes)
+		if err != nil {
+			return nil, fmt.Errorf("unable to extract key pair from bytes: %w", err)
+		}
+		defer kp.Wipe()
+
+		sig, _ := kp.Sign(nonce)
+		return sig, nil
+	}
+
+	return UserJWT(userCB, sigCB)
+}
+
 // UserJWTAndSeed is a convenience function that takes the JWT and seed
 // values as strings.
 func UserJWTAndSeed(jwt string, seed string) Option {


Test patch:
diff --git a/test/nats_test.go b/test/nats_test.go
index 15bf492..873acc2 100644
--- a/test/nats_test.go
+++ b/test/nats_test.go
@@ -336,6 +336,28 @@ func TestUserJWTAndSeed(t *testing.T) {
 	nc.Close()
 }
 
+func TestUserCredentialBytes(t *testing.T) {
+	ts := runTrustServer()
+	defer ts.Shutdown()
+
+	// Test with JWT and seed as byte slices
+	jwtBytes := []byte(uJWT)
+	seedBytes := uSeed
+
+	nc, err := nats.Connect(ts.ClientURL(), nats.UserCredentialBytes(jwtBytes, seedBytes))
+	if err != nil {
+		t.Fatalf("Expected to connect with separate JWT and seed bytes, got %v", err)
+	}
+	nc.Close()
+
+	// Test with chained credentials (JWT and seed in same byte slice)
+	nc2, err := nats.Connect(ts.ClientURL(), nats.UserCredentialBytes([]byte(chained)))
+	if err != nil {
+		t.Fatalf("Expected to connect with chained credentials bytes, got %v", err)
+	}
+	nc2.Close()
+}
+
 // If we are using TLS and have multiple servers we try to match the IP
 // from a discovered server with the expected hostname for certs without IP
 // designations. In certain cases where there is a not authorized error and