Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ type AuthorityConfiguration struct {
// Required: Type of authority
AuthorityType AuthorityType // "default", "multi-tenant", or "custom"

// Required: Azure AD tenant ID
// Use "common" for multi-tenant applications
// Required for default authority: Azure AD tenant ID
// Optional for multi-tenant authority (uses "common" endpoint)
TenantID string

// Optional: Custom authority URL
Expand Down Expand Up @@ -388,7 +388,6 @@ options := entraid.CredentialsProviderOptions{
// Multi-tenant application
authority := identity.AuthorityConfiguration{
AuthorityType: identity.AuthorityTypeMultiTenant,
TenantID: "common",
}

// Single-tenant application
Expand Down
6 changes: 3 additions & 3 deletions credentials_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestCredentialsProviderErrorScenarios(t *testing.T) {
CredentialsType: "invalid-type", // Invalid credentials type
ClientSecret: "test-secret",
Scopes: []string{identity.RedisScopeDefault},
Authority: identity.AuthorityConfiguration{},
Authority: identity.AuthorityConfiguration{AuthorityType: identity.AuthorityTypeMultiTenant},
},
}

Expand All @@ -74,7 +74,7 @@ func TestCredentialsProviderErrorScenarios(t *testing.T) {
CredentialsType: identity.ClientSecretCredentialType,
ClientSecret: "", // Empty client secret
Scopes: []string{identity.RedisScopeDefault},
Authority: identity.AuthorityConfiguration{},
Authority: identity.AuthorityConfiguration{AuthorityType: identity.AuthorityTypeMultiTenant},
},
}

Expand Down Expand Up @@ -336,7 +336,7 @@ func TestCredentialsProviderSubscribe(t *testing.T) {
CredentialsType: identity.ClientSecretCredentialType,
ClientSecret: "test-secret",
Scopes: []string{identity.RedisScopeDefault},
Authority: identity.AuthorityConfiguration{},
Authority: identity.AuthorityConfiguration{AuthorityType: identity.AuthorityTypeMultiTenant},
},
}
t.Run("double subscribe and cancel resubscribe", func(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion examples/entraid/clientcert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func main() {
ClientSecret: cfg.AzureClientSecret,
CredentialsType: identity.ClientCertificateCredentialType,
Authority: identity.AuthorityConfiguration{
AuthorityType: identity.AuthorityTypeMultiTenant,
AuthorityType: identity.AuthorityTypeDefault,
TenantID: cfg.AzureTenantID,
},
Scopes: cfg.GetRedisScopes(),
Expand Down
2 changes: 1 addition & 1 deletion examples/entraid/clientsecret/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func main() {
ClientSecret: cfg.AzureClientSecret,
CredentialsType: identity.ClientSecretCredentialType,
Authority: identity.AuthorityConfiguration{
AuthorityType: identity.AuthorityTypeMultiTenant,
AuthorityType: identity.AuthorityTypeDefault,
TenantID: cfg.AzureTenantID,
},
Scopes: cfg.GetRedisScopes(),
Expand Down
2 changes: 1 addition & 1 deletion examples/entraid/config/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module config

go 1.23.4
go 1.23.4
38 changes: 14 additions & 24 deletions examples/entraid/managedidentity_systemassigned_min/go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 h1:j8BorDEigD8UFOSZQiSqAMOOleyQOOQPnUAwV+Ls1gA=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
Expand All @@ -24,38 +20,32 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b h1:I6Y+sXfQLIUo8vkx+EcuTcAcs0ZnPceNe8cdQ0HsjQI=
github.com/redis/go-redis/v9 v9.5.3-0.20250519143649-1628b87c162b/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20 changes: 11 additions & 9 deletions identity/authority_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package identity
import "fmt"

const (
// AuthorityTypeDefault is the default authority type.
// This is used to specify the authority type when requesting a token.
// AuthorityTypeDefault is the default authority type for single-tenant applications.
// This type requires a specific tenant ID and constructs the authority URL as:
// https://login.microsoftonline.com/{tenantID}
AuthorityTypeDefault = "default"
// AuthorityTypeMultiTenant is the multi-tenant authority type.
// This is used to specify the multi-tenant authority type when requesting a token.
// This type of authority is used to authenticate the identity when requesting a token.
// This type uses the "common" endpoint and allows authentication from any Azure AD tenant:
// https://login.microsoftonline.com/common
AuthorityTypeMultiTenant = "multi-tenant"
// AuthorityTypeCustom is the custom authority type.
// This is used to specify the custom authority type when requesting a token.
// This type allows specifying a custom authority URL for specialized scenarios.
AuthorityTypeCustom = "custom"
)

Expand All @@ -28,7 +29,8 @@ type AuthorityConfiguration struct {
Authority string

// TenantID is the tenant ID of the identity provider.
// This is used to identify the tenant when requesting a token.
// Required for AuthorityTypeDefault to identify the specific Azure AD tenant.
// Optional for AuthorityTypeMultiTenant (ignored, uses "common" endpoint).
// This is typically the ID of the Azure Active Directory tenant.
TenantID string
}
Expand All @@ -42,12 +44,12 @@ func (a AuthorityConfiguration) getAuthority() (string, error) {

switch a.AuthorityType {
case AuthorityTypeDefault:
return "https://login.microsoftonline.com/common", nil
case AuthorityTypeMultiTenant:
if a.TenantID == "" {
return "", fmt.Errorf("tenant ID is required when using multi-tenant authority type")
return "", fmt.Errorf("tenant ID is required when using default authority type")
}
return fmt.Sprintf("https://login.microsoftonline.com/%s", a.TenantID), nil
case AuthorityTypeMultiTenant:
return "https://login.microsoftonline.com/common", nil
case AuthorityTypeCustom:
if a.Authority == "" {
return "", fmt.Errorf("authority is required when using custom authority type")
Expand Down
78 changes: 64 additions & 14 deletions identity/authority_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ func TestAuthorityConfiguration(t *testing.T) {
{
name: "Default Authority",
authorityType: AuthorityTypeDefault,
expected: "https://login.microsoftonline.com/common",
tenantID: "12345",
expected: "https://login.microsoftonline.com/12345",
expectError: false,
},
{
name: "Multi-Tenant Authority",
authorityType: AuthorityTypeMultiTenant,
tenantID: "12345",
expected: "https://login.microsoftonline.com/12345",
expected: "https://login.microsoftonline.com/common",
expectError: false,
},
{
Expand All @@ -42,8 +42,8 @@ func TestAuthorityConfiguration(t *testing.T) {
expectError: true,
},
{
name: "Missing Tenant ID for Multi-Tenant",
authorityType: AuthorityTypeMultiTenant,
name: "Missing Tenant ID for Default",
authorityType: AuthorityTypeDefault,
expectError: true,
},
{
Expand All @@ -52,8 +52,8 @@ func TestAuthorityConfiguration(t *testing.T) {
expectError: true,
},
{
name: "Default Authority Type with Tenant ID",
authorityType: AuthorityTypeDefault,
name: "Multi-Tenant Authority Type with Tenant ID",
authorityType: AuthorityTypeMultiTenant,
tenantID: "12345",
expected: "https://login.microsoftonline.com/common",
expectError: false,
Expand All @@ -80,21 +80,23 @@ func TestAuthorityConfiguration(t *testing.T) {

func TestAuthorityConfigurationDefault(t *testing.T) {
t.Parallel()
ac := AuthorityConfiguration{}
ac := AuthorityConfiguration{
AuthorityType: AuthorityTypeDefault,
TenantID: "12345",
}
result, err := ac.getAuthority()
assert.NoError(t, err)
assert.Equal(t, "https://login.microsoftonline.com/common", result)
assert.Equal(t, "https://login.microsoftonline.com/12345", result)
}

func TestAuthorityConfigurationMultiTenant(t *testing.T) {
t.Parallel()
ac := AuthorityConfiguration{
AuthorityType: AuthorityTypeMultiTenant,
TenantID: "12345",
}
result, err := ac.getAuthority()
assert.NoError(t, err)
assert.Equal(t, "https://login.microsoftonline.com/12345", result)
assert.Equal(t, "https://login.microsoftonline.com/common", result)
}

func TestAuthorityConfigurationCustom(t *testing.T) {
Expand All @@ -121,7 +123,7 @@ func TestAuthorityConfigurationInvalid(t *testing.T) {
func TestAuthorityConfigurationMissingTenantID(t *testing.T) {
t.Parallel()
ac := AuthorityConfiguration{
AuthorityType: AuthorityTypeMultiTenant,
AuthorityType: AuthorityTypeDefault,
}
result, err := ac.getAuthority()
assert.Error(t, err)
Expand All @@ -145,7 +147,7 @@ func TestAuthorityConfigurationDefaultAuthorityType(t *testing.T) {
}
result, err := ac.getAuthority()
assert.NoError(t, err)
assert.Equal(t, "https://login.microsoftonline.com/common", result)
assert.Equal(t, "https://login.microsoftonline.com/12345", result)
}

func TestAuthorityConfigurationDefaultAuthorityTypeWithTenantID(t *testing.T) {
Expand All @@ -156,5 +158,53 @@ func TestAuthorityConfigurationDefaultAuthorityTypeWithTenantID(t *testing.T) {
}
result, err := ac.getAuthority()
assert.NoError(t, err)
assert.Equal(t, "https://login.microsoftonline.com/common", result)
assert.Equal(t, "https://login.microsoftonline.com/12345", result)
}

// TestIssueScenario tests the exact scenario reported in the GitHub issue:
// Single-tenant application should use AuthorityTypeDefault with a specific tenant ID
// and should produce a tenant-specific authority URL (not /common)
func TestIssueScenario(t *testing.T) {
t.Parallel()

// This is the configuration from the issue report
// that should produce a tenant-specific authority URL
tenantID := "test-tenant-id-123"

// Single-tenant application configuration as documented
ac := AuthorityConfiguration{
AuthorityType: AuthorityTypeDefault,
TenantID: tenantID,
}

authority, err := ac.getAuthority()

// Should not error
assert.NoError(t, err)

// Should produce tenant-specific URL, NOT /common
expectedAuthority := "https://login.microsoftonline.com/test-tenant-id-123"
assert.Equal(t, expectedAuthority, authority, "Single-tenant application should use tenant-specific authority URL")

// Verify it's NOT the common endpoint
assert.NotEqual(t, "https://login.microsoftonline.com/common", authority, "Single-tenant application should NOT use /common endpoint")
}

// TestMultiTenantScenario tests that multi-tenant applications use the common endpoint
func TestMultiTenantScenario(t *testing.T) {
t.Parallel()

// Multi-tenant application configuration as documented
ac := AuthorityConfiguration{
AuthorityType: AuthorityTypeMultiTenant,
}

authority, err := ac.getAuthority()

// Should not error
assert.NoError(t, err)

// Should produce /common URL for multi-tenant
expectedAuthority := "https://login.microsoftonline.com/common"
assert.Equal(t, expectedAuthority, authority, "Multi-tenant application should use /common endpoint")
}
Loading
Loading