Skip to content

Commit ca5792e

Browse files
authored
feat: support multiple aud for the external providers (#2117)
## What kind of change does this PR introduce? Support multiple values in `aud` claim in the provider token. It can be either string or array of string (complying with JWT [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3))
1 parent 7507822 commit ca5792e

File tree

2 files changed

+146
-5
lines changed

2 files changed

+146
-5
lines changed

internal/api/provider/oidc_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto"
66
"crypto/rsa"
77
"encoding/base64"
8+
"encoding/json"
89
"math/big"
910
"testing"
1011
"time"
@@ -188,3 +189,127 @@ func TestAzureIDTokenClaimsIsEmailVerified(t *testing.T) {
188189
}
189190
}
190191
}
192+
193+
func TestAudienceUnmarshalJSON(t *testing.T) {
194+
tests := []struct {
195+
name string
196+
json string
197+
expected audience
198+
wantErr bool
199+
}{
200+
{
201+
name: "string audience",
202+
json: `"client-id-123"`,
203+
expected: audience{"client-id-123"},
204+
wantErr: false,
205+
},
206+
{
207+
name: "array audience with single element",
208+
json: `["client-id-123"]`,
209+
expected: audience{"client-id-123"},
210+
wantErr: false,
211+
},
212+
{
213+
name: "array audience with multiple elements",
214+
json: `["client-id-123", "client-id-456", "client-id-789"]`,
215+
expected: audience{"client-id-123", "client-id-456", "client-id-789"},
216+
wantErr: false,
217+
},
218+
{
219+
name: "empty array",
220+
json: `[]`,
221+
expected: audience{},
222+
wantErr: false,
223+
},
224+
{
225+
name: "invalid JSON",
226+
json: `{invalid}`,
227+
wantErr: true,
228+
},
229+
{
230+
name: "null value",
231+
json: `null`,
232+
expected: audience{""},
233+
wantErr: false,
234+
},
235+
{
236+
name: "number value",
237+
json: `123`,
238+
wantErr: true,
239+
},
240+
{
241+
name: "boolean value",
242+
json: `true`,
243+
wantErr: true,
244+
},
245+
}
246+
247+
for _, tt := range tests {
248+
t.Run(tt.name, func(t *testing.T) {
249+
var aud audience
250+
err := json.Unmarshal([]byte(tt.json), &aud)
251+
252+
if tt.wantErr {
253+
require.Error(t, err)
254+
return
255+
}
256+
257+
require.NoError(t, err)
258+
require.Equal(t, tt.expected, aud)
259+
})
260+
}
261+
}
262+
263+
func TestClaimsAudienceUnmarshal(t *testing.T) {
264+
tests := []struct {
265+
name string
266+
json string
267+
expected audience
268+
wantErr bool
269+
}{
270+
{
271+
name: "claims with string audience",
272+
json: `{
273+
"iss": "https://example.com",
274+
"sub": "user123",
275+
"aud": "client-id-123"
276+
}`,
277+
expected: audience{"client-id-123"},
278+
wantErr: false,
279+
},
280+
{
281+
name: "claims with array audience",
282+
json: `{
283+
"iss": "https://example.com",
284+
"sub": "user123",
285+
"aud": ["client-id-123", "client-id-456"]
286+
}`,
287+
expected: audience{"client-id-123", "client-id-456"},
288+
wantErr: false,
289+
},
290+
{
291+
name: "claims with missing audience",
292+
json: `{
293+
"iss": "https://example.com",
294+
"sub": "user123"
295+
}`,
296+
expected: audience(nil),
297+
wantErr: false,
298+
},
299+
}
300+
301+
for _, tt := range tests {
302+
t.Run(tt.name, func(t *testing.T) {
303+
var claims Claims
304+
err := json.Unmarshal([]byte(tt.json), &claims)
305+
306+
if tt.wantErr {
307+
require.Error(t, err)
308+
return
309+
}
310+
311+
require.NoError(t, err)
312+
require.Equal(t, tt.expected, claims.Aud)
313+
})
314+
}
315+
}

internal/api/provider/provider.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,29 @@ func init() {
2727
}
2828
}
2929

30+
type audience []string
31+
32+
func (a *audience) UnmarshalJSON(b []byte) error {
33+
var s string
34+
if json.Unmarshal(b, &s) == nil {
35+
*a = audience{s}
36+
return nil
37+
}
38+
var auds []string
39+
if err := json.Unmarshal(b, &auds); err != nil {
40+
return err
41+
}
42+
*a = auds
43+
return nil
44+
}
45+
3046
type Claims struct {
3147
// Reserved claims
32-
Issuer string `json:"iss,omitempty" structs:"iss,omitempty"`
33-
Subject string `json:"sub,omitempty" structs:"sub,omitempty"`
34-
Aud string `json:"aud,omitempty" structs:"aud,omitempty"`
35-
Iat float64 `json:"iat,omitempty" structs:"iat,omitempty"`
36-
Exp float64 `json:"exp,omitempty" structs:"exp,omitempty"`
48+
Issuer string `json:"iss,omitempty" structs:"iss,omitempty"`
49+
Subject string `json:"sub,omitempty" structs:"sub,omitempty"`
50+
Aud audience `json:"aud,omitempty" structs:"aud,omitempty"`
51+
Iat float64 `json:"iat,omitempty" structs:"iat,omitempty"`
52+
Exp float64 `json:"exp,omitempty" structs:"exp,omitempty"`
3753

3854
// Default profile claims
3955
Name string `json:"name,omitempty" structs:"name,omitempty"`

0 commit comments

Comments
 (0)