Skip to content

Commit 86d418f

Browse files
authored
feat: Introducing StreamingCredentialsProvider for token based authentication (#3320)
* wip * update documentation * add streamingcredentialsprovider in options * fix: put back option in pool creation * add package level comment * Initial re authentication implementation Introduces the StreamingCredentialsProvider as the CredentialsProvider with the highest priority. TODO: needs to be tested * Change function type name Change CancelProviderFunc to UnsubscribeFunc * add tests * fix race in tests * fix example tests * wip, hooks refactor * fix build * update README.md * update wordlist * update README.md * refactor(auth): early returns in cred listener * fix(doctest): simulate some delay * feat(conn): add close hook on conn * fix(tests): simulate start/stop in mock credentials provider * fix(auth): don't double close the conn * docs(README): mark streaming credentials provider as experimental * fix(auth): streamline auth err proccess * fix(auth): check err on close conn * chore(entraid): use the repo under redis org
1 parent 28a3c97 commit 86d418f

20 files changed

+1103
-130
lines changed

.github/wordlist.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,12 @@ RedisGears
6565
RedisTimeseries
6666
RediSearch
6767
RawResult
68-
RawVal
68+
RawVal
69+
entra
70+
EntraID
71+
Entra
72+
OAuth
73+
Azure
74+
StreamingCredentialsProvider
75+
oauth
76+
entraid

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ testdata/*
77
redis8tests.sh
88
coverage.txt
99
**/coverage.txt
10-
.vscode
10+
.vscode
11+
tmp/*

README.md

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ key value NoSQL database that uses RocksDB as storage engine and is compatible w
6868

6969
- Redis commands except QUIT and SYNC.
7070
- Automatic connection pooling.
71+
- [StreamingCredentialsProvider (e.g. entra id, oauth)](#1-streaming-credentials-provider-highest-priority) (experimental)
7172
- [Pub/Sub](https://redis.uptrace.dev/guide/go-redis-pubsub.html).
7273
- [Pipelines and transactions](https://redis.uptrace.dev/guide/go-redis-pipelines.html).
7374
- [Scripting](https://redis.uptrace.dev/guide/lua-scripting.html).
@@ -136,17 +137,121 @@ func ExampleClient() {
136137
}
137138
```
138139

139-
The above can be modified to specify the version of the RESP protocol by adding the `protocol`
140-
option to the `Options` struct:
140+
### Authentication
141+
142+
The Redis client supports multiple ways to provide authentication credentials, with a clear priority order. Here are the available options:
143+
144+
#### 1. Streaming Credentials Provider (Highest Priority) - Experimental feature
145+
146+
The streaming credentials provider allows for dynamic credential updates during the connection lifetime. This is particularly useful for managed identity services and token-based authentication.
141147

142148
```go
143-
rdb := redis.NewClient(&redis.Options{
144-
Addr: "localhost:6379",
145-
Password: "", // no password set
146-
DB: 0, // use default DB
147-
Protocol: 3, // specify 2 for RESP 2 or 3 for RESP 3
148-
})
149+
type StreamingCredentialsProvider interface {
150+
Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
151+
}
152+
153+
type CredentialsListener interface {
154+
OnNext(credentials Credentials) // Called when credentials are updated
155+
OnError(err error) // Called when an error occurs
156+
}
157+
158+
type Credentials interface {
159+
BasicAuth() (username string, password string)
160+
RawCredentials() string
161+
}
162+
```
163+
164+
Example usage:
165+
```go
166+
rdb := redis.NewClient(&redis.Options{
167+
Addr: "localhost:6379",
168+
StreamingCredentialsProvider: &MyCredentialsProvider{},
169+
})
170+
```
171+
172+
**Note:** The streaming credentials provider can be used with [go-redis-entraid](https://github.com/redis/go-redis-entraid) to enable Entra ID (formerly Azure AD) authentication. This allows for seamless integration with Azure's managed identity services and token-based authentication.
173+
174+
Example with Entra ID:
175+
```go
176+
import (
177+
"github.com/redis/go-redis/v9"
178+
"github.com/redis/go-redis-entraid"
179+
)
180+
181+
// Create an Entra ID credentials provider
182+
provider := entraid.NewDefaultAzureIdentityProvider()
183+
184+
// Configure Redis client with Entra ID authentication
185+
rdb := redis.NewClient(&redis.Options{
186+
Addr: "your-redis-server.redis.cache.windows.net:6380",
187+
StreamingCredentialsProvider: provider,
188+
TLSConfig: &tls.Config{
189+
MinVersion: tls.VersionTLS12,
190+
},
191+
})
192+
```
149193

194+
#### 2. Context-based Credentials Provider
195+
196+
The context-based provider allows credentials to be determined at the time of each operation, using the context.
197+
198+
```go
199+
rdb := redis.NewClient(&redis.Options{
200+
Addr: "localhost:6379",
201+
CredentialsProviderContext: func(ctx context.Context) (string, string, error) {
202+
// Return username, password, and any error
203+
return "user", "pass", nil
204+
},
205+
})
206+
```
207+
208+
#### 3. Regular Credentials Provider
209+
210+
A simple function-based provider that returns static credentials.
211+
212+
```go
213+
rdb := redis.NewClient(&redis.Options{
214+
Addr: "localhost:6379",
215+
CredentialsProvider: func() (string, string) {
216+
// Return username and password
217+
return "user", "pass"
218+
},
219+
})
220+
```
221+
222+
#### 4. Username/Password Fields (Lowest Priority)
223+
224+
The most basic way to provide credentials is through the `Username` and `Password` fields in the options.
225+
226+
```go
227+
rdb := redis.NewClient(&redis.Options{
228+
Addr: "localhost:6379",
229+
Username: "user",
230+
Password: "pass",
231+
})
232+
```
233+
234+
#### Priority Order
235+
236+
The client will use credentials in the following priority order:
237+
1. Streaming Credentials Provider (if set)
238+
2. Context-based Credentials Provider (if set)
239+
3. Regular Credentials Provider (if set)
240+
4. Username/Password fields (if set)
241+
242+
If none of these are set, the client will attempt to connect without authentication.
243+
244+
### Protocol Version
245+
246+
The client supports both RESP2 and RESP3 protocols. You can specify the protocol version in the options:
247+
248+
```go
249+
rdb := redis.NewClient(&redis.Options{
250+
Addr: "localhost:6379",
251+
Password: "", // no password set
252+
DB: 0, // use default DB
253+
Protocol: 3, // specify 2 for RESP 2 or 3 for RESP 3
254+
})
150255
```
151256

152257
### Connecting via a redis url

auth/auth.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Package auth package provides authentication-related interfaces and types.
2+
// It also includes a basic implementation of credentials using username and password.
3+
package auth
4+
5+
// StreamingCredentialsProvider is an interface that defines the methods for a streaming credentials provider.
6+
// It is used to provide credentials for authentication.
7+
// The CredentialsListener is used to receive updates when the credentials change.
8+
type StreamingCredentialsProvider interface {
9+
// Subscribe subscribes to the credentials provider for updates.
10+
// It returns the current credentials, a cancel function to unsubscribe from the provider,
11+
// and an error if any.
12+
// TODO(ndyakov): Should we add context to the Subscribe method?
13+
Subscribe(listener CredentialsListener) (Credentials, UnsubscribeFunc, error)
14+
}
15+
16+
// UnsubscribeFunc is a function that is used to cancel the subscription to the credentials provider.
17+
// It is used to unsubscribe from the provider when the credentials are no longer needed.
18+
type UnsubscribeFunc func() error
19+
20+
// CredentialsListener is an interface that defines the methods for a credentials listener.
21+
// It is used to receive updates when the credentials change.
22+
// The OnNext method is called when the credentials change.
23+
// The OnError method is called when an error occurs while requesting the credentials.
24+
type CredentialsListener interface {
25+
OnNext(credentials Credentials)
26+
OnError(err error)
27+
}
28+
29+
// Credentials is an interface that defines the methods for credentials.
30+
// It is used to provide the credentials for authentication.
31+
type Credentials interface {
32+
// BasicAuth returns the username and password for basic authentication.
33+
BasicAuth() (username string, password string)
34+
// RawCredentials returns the raw credentials as a string.
35+
// This can be used to extract the username and password from the raw credentials or
36+
// additional information if present in the token.
37+
RawCredentials() string
38+
}
39+
40+
type basicAuth struct {
41+
username string
42+
password string
43+
}
44+
45+
// RawCredentials returns the raw credentials as a string.
46+
func (b *basicAuth) RawCredentials() string {
47+
return b.username + ":" + b.password
48+
}
49+
50+
// BasicAuth returns the username and password for basic authentication.
51+
func (b *basicAuth) BasicAuth() (username string, password string) {
52+
return b.username, b.password
53+
}
54+
55+
// NewBasicCredentials creates a new Credentials object from the given username and password.
56+
func NewBasicCredentials(username, password string) Credentials {
57+
return &basicAuth{
58+
username: username,
59+
password: password,
60+
}
61+
}

0 commit comments

Comments
 (0)