1
+ import { DefaultAzureCredential , EnvironmentCredential } from '@azure/identity' ;
1
2
import { BasicAuth } from '@redis/client/dist/lib/authx' ;
2
3
import { createClient } from '@redis/client' ;
3
- import { EntraIdCredentialsProviderFactory } from '../lib/entra-id-credentials-provider-factory' ;
4
+ import { EntraIdCredentialsProviderFactory , REDIS_SCOPE_DEFAULT } from '../lib/entra-id-credentials-provider-factory' ;
4
5
import { strict as assert } from 'node:assert' ;
5
6
import { spy , SinonSpy } from 'sinon' ;
6
7
import { randomUUID } from 'crypto' ;
@@ -51,6 +52,36 @@ describe('EntraID Integration Tests', () => {
51
52
) ;
52
53
} ) ;
53
54
55
+ it ( 'client with DefaultAzureCredential should be able to authenticate/re-authenticate' , async ( ) => {
56
+ const config = await readConfigFromEnv ( ) ;
57
+
58
+ const azureCredential = new DefaultAzureCredential ( ) ;
59
+
60
+ await runAuthenticationTest ( ( ) =>
61
+ EntraIdCredentialsProviderFactory . createForDefaultAzureCredential ( {
62
+ credential : azureCredential ,
63
+ scopes : REDIS_SCOPE_DEFAULT ,
64
+ tokenManagerConfig : {
65
+ expirationRefreshRatio : 0.00001
66
+ }
67
+ } )
68
+ , { testingDefaultAzureCredential : true } ) ;
69
+ } ) ;
70
+
71
+ it ( 'client with EnvironmentCredential should be able to authenticate/re-authenticate' , async ( ) => {
72
+ const envCredential = new EnvironmentCredential ( ) ;
73
+
74
+ await runAuthenticationTest ( ( ) =>
75
+ EntraIdCredentialsProviderFactory . createForDefaultAzureCredential ( {
76
+ credential : envCredential ,
77
+ scopes : REDIS_SCOPE_DEFAULT ,
78
+ tokenManagerConfig : {
79
+ expirationRefreshRatio : 0.00001
80
+ }
81
+ } )
82
+ , { testingDefaultAzureCredential : true } ) ;
83
+ } ) ;
84
+
54
85
interface TestConfig {
55
86
clientId : string ;
56
87
clientSecret : string ;
@@ -83,15 +114,15 @@ describe('EntraID Integration Tests', () => {
83
114
} ) ;
84
115
85
116
return {
86
- endpoints : await loadFromFile ( requiredEnvVars . REDIS_ENDPOINTS_CONFIG_PATH ) ,
87
- clientId : requiredEnvVars . AZURE_CLIENT_ID ,
88
- clientSecret : requiredEnvVars . AZURE_CLIENT_SECRET ,
89
- authority : requiredEnvVars . AZURE_AUTHORITY ,
90
- tenantId : requiredEnvVars . AZURE_TENANT_ID ,
91
- redisScopes : requiredEnvVars . AZURE_REDIS_SCOPES ,
92
- cert : requiredEnvVars . AZURE_CERT ,
93
- privateKey : requiredEnvVars . AZURE_PRIVATE_KEY ,
94
- userAssignedManagedId : requiredEnvVars . AZURE_USER_ASSIGNED_MANAGED_ID
117
+ endpoints : await loadFromFile ( requiredEnvVars . REDIS_ENDPOINTS_CONFIG_PATH as string ) ,
118
+ clientId : requiredEnvVars . AZURE_CLIENT_ID as string ,
119
+ clientSecret : requiredEnvVars . AZURE_CLIENT_SECRET as string ,
120
+ authority : requiredEnvVars . AZURE_AUTHORITY as string ,
121
+ tenantId : requiredEnvVars . AZURE_TENANT_ID as string ,
122
+ redisScopes : requiredEnvVars . AZURE_REDIS_SCOPES as string ,
123
+ cert : requiredEnvVars . AZURE_CERT as string ,
124
+ privateKey : requiredEnvVars . AZURE_PRIVATE_KEY as string ,
125
+ userAssignedManagedId : requiredEnvVars . AZURE_USER_ASSIGNED_MANAGED_ID as string
95
126
} ;
96
127
} ;
97
128
@@ -127,12 +158,22 @@ describe('EntraID Integration Tests', () => {
127
158
}
128
159
} ;
129
160
130
- const validateTokens = ( reAuthSpy : SinonSpy ) => {
161
+ /**
162
+ * Validates authentication tokens generated during re-authentication
163
+ *
164
+ * @param reAuthSpy - The Sinon spy on the reAuthenticate method
165
+ * @param skipUniqueCheckForDefaultAzureCredential - Skip the unique check for DefaultAzureCredential as there are no guarantees that the tokens will be unique
166
+ * if the test is using default azure credential
167
+ */
168
+ const validateTokens = ( reAuthSpy : SinonSpy , skipUniqueCheckForDefaultAzureCredential : boolean ) => {
131
169
assert ( reAuthSpy . callCount >= 1 ,
132
170
`reAuthenticate should have been called at least once, but was called ${ reAuthSpy . callCount } times` ) ;
133
171
134
172
const tokenDetails : TokenDetail [ ] = reAuthSpy . getCalls ( ) . map ( call => {
135
173
const creds = call . args [ 0 ] as BasicAuth ;
174
+ if ( ! creds . password ) {
175
+ throw new Error ( 'Expected password to be set in BasicAuth credentials' ) ;
176
+ }
136
177
const tokenPayload = JSON . parse (
137
178
Buffer . from ( creds . password . split ( '.' ) [ 1 ] , 'base64' ) . toString ( )
138
179
) ;
@@ -146,38 +187,43 @@ describe('EntraID Integration Tests', () => {
146
187
} ;
147
188
} ) ;
148
189
149
- // Verify unique tokens
150
- const uniqueTokens = new Set ( tokenDetails . map ( detail => detail . token ) ) ;
151
- assert . equal (
152
- uniqueTokens . size ,
153
- reAuthSpy . callCount ,
154
- `Expected ${ reAuthSpy . callCount } different tokens, but got ${ uniqueTokens . size } unique tokens`
155
- ) ;
190
+ // we can't guarantee that the tokens will be unique when using DefaultAzureCredential
191
+ if ( ! skipUniqueCheckForDefaultAzureCredential ) {
192
+ // Verify unique tokens
193
+ const uniqueTokens = new Set ( tokenDetails . map ( detail => detail . token ) ) ;
194
+ assert . equal (
195
+ uniqueTokens . size ,
196
+ reAuthSpy . callCount ,
197
+ `Expected ${ reAuthSpy . callCount } different tokens, but got ${ uniqueTokens . size } unique tokens`
198
+ ) ;
156
199
157
- // Verify all tokens are not cached (i.e. have the same lifetime)
158
- const uniqueLifetimes = new Set ( tokenDetails . map ( detail => detail . lifetime ) ) ;
159
- assert . equal (
160
- uniqueLifetimes . size ,
161
- 1 ,
162
- `Expected all tokens to have the same lifetime, but found ${ uniqueLifetimes . size } different lifetimes: ${ [ uniqueLifetimes ] . join ( ', ' ) } seconds`
163
- ) ;
200
+ // Verify all tokens are not cached (i.e. have the same lifetime)
201
+ const uniqueLifetimes = new Set ( tokenDetails . map ( detail => detail . lifetime ) ) ;
202
+ assert . equal (
203
+ uniqueLifetimes . size ,
204
+ 1 ,
205
+ `Expected all tokens to have the same lifetime, but found ${ uniqueLifetimes . size } different lifetimes: ${ ( Array . from ( uniqueLifetimes ) . join ( ',' ) ) } seconds`
206
+ ) ;
164
207
165
- // Verify that all tokens have different uti (unique token identifier)
166
- const uniqueUti = new Set ( tokenDetails . map ( detail => detail . uti ) ) ;
167
- assert . equal (
168
- uniqueUti . size ,
169
- reAuthSpy . callCount ,
170
- `Expected all tokens to have different uti, but found ${ uniqueUti . size } different uti in: ${ [ uniqueUti ] . join ( ', ' ) } `
171
- ) ;
208
+ // Verify that all tokens have different uti (unique token identifier)
209
+ const uniqueUti = new Set ( tokenDetails . map ( detail => detail . uti ) ) ;
210
+ assert . equal (
211
+ uniqueUti . size ,
212
+ reAuthSpy . callCount ,
213
+ `Expected all tokens to have different uti, but found ${ uniqueUti . size } different uti in: ${ ( Array . from ( uniqueUti ) . join ( ',' ) ) } `
214
+ ) ;
215
+ }
172
216
} ;
173
217
174
- const runAuthenticationTest = async ( setupCredentialsProvider : ( ) => any ) => {
218
+ const runAuthenticationTest = async ( setupCredentialsProvider : ( ) => any , options : {
219
+ testingDefaultAzureCredential : boolean
220
+ } = { testingDefaultAzureCredential : false } ) => {
175
221
const { client, reAuthSpy } = await setupTestClient ( setupCredentialsProvider ( ) ) ;
176
222
177
223
try {
178
224
await client . connect ( ) ;
179
225
await runClientOperations ( client ) ;
180
- validateTokens ( reAuthSpy ) ;
226
+ validateTokens ( reAuthSpy , options . testingDefaultAzureCredential ) ;
181
227
} finally {
182
228
await client . destroy ( ) ;
183
229
}
0 commit comments