Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions sdk/azidentity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## 1.6.0-beta.5 (Unreleased)

### Features Added
* `NewOnBehalfOfCredentialWithClientAssertions` creates an on-behalf-of credential
that authenticates with client assertions such as federated credentials

### Breaking Changes

Expand Down
4 changes: 3 additions & 1 deletion sdk/azidentity/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ func ExampleNewOnBehalfOfCredentialWithCertificate() {
// TODO: handle error
}

cred, err = azidentity.NewClientCertificateCredential(tenantID, clientID, certs, key, nil)
// userAssertion is the user's access token for the application. Typically it comes from a client request.
userAssertion := "TODO"
cred, err = azidentity.NewOnBehalfOfCredentialWithCertificate(tenantID, clientID, userAssertion, certs, key, nil)
Comment thread
jhendrixMSFT marked this conversation as resolved.
if err != nil {
// TODO: handle error
}
Expand Down
10 changes: 10 additions & 0 deletions sdk/azidentity/on_behalf_of_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ func NewOnBehalfOfCredentialWithCertificate(tenantID, clientID, userAssertion st
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
}

// NewOnBehalfOfCredentialWithClientAssertions constructs an OnBehalfOfCredential that authenticates with client assertions.
// userAssertion is the user's access token for the application. The getAssertion function should return client assertions
// that authenticate the application to Microsoft Entra ID, such as federated credentials.
func NewOnBehalfOfCredentialWithClientAssertions(tenantID, clientID, userAssertion string, getAssertion func(context.Context) (string, error), options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
cred := confidential.NewCredFromAssertionCallback(func(ctx context.Context, _ confidential.AssertionRequestOptions) (string, error) {
return getAssertion(ctx)
})
return newOnBehalfOfCredential(tenantID, clientID, userAssertion, cred, options)
}

// NewOnBehalfOfCredentialWithSecret constructs an OnBehalfOfCredential that authenticates with a client secret.
func NewOnBehalfOfCredentialWithSecret(tenantID, clientID, userAssertion, clientSecret string, options *OnBehalfOfCredentialOptions) (*OnBehalfOfCredential, error) {
cred, err := confidential.NewCredFromSecret(clientSecret)
Expand Down
44 changes: 35 additions & 9 deletions sdk/azidentity/on_behalf_of_credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,62 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/stretchr/testify/require"
)

func TestOnBehalfOfCredential(t *testing.T) {
expectedAssertion := "user-assertion"
clientAssertion := "client-assertion"
userAssertion := "user-assertion"
certs, key := allCertTests[0].certs, allCertTests[0].key
for _, test := range []struct {
ctor func(policy.Transporter) (*OnBehalfOfCredential, error)
name string
sendX5C bool
ctor func(policy.Transporter) (*OnBehalfOfCredential, error)
name string
sendX5C bool
verifyCredential func(*testing.T, *http.Request)
}{
{
ctor: func(tp policy.Transporter) (*OnBehalfOfCredential, error) {
o := OnBehalfOfCredentialOptions{ClientOptions: policy.ClientOptions{Transport: tp}}
return NewOnBehalfOfCredentialWithCertificate(fakeTenantID, fakeClientID, expectedAssertion, certs, key, &o)
getAssertions := func(context.Context) (string, error) {
return clientAssertion, nil
}
return NewOnBehalfOfCredentialWithClientAssertions(fakeTenantID, fakeClientID, userAssertion, getAssertions, &o)
},
name: "client assertions",
verifyCredential: func(t *testing.T, r *http.Request) {
require.Equal(t, clientAssertion, r.FormValue("client_assertion"))
},
},
{
ctor: func(tp policy.Transporter) (*OnBehalfOfCredential, error) {
o := OnBehalfOfCredentialOptions{ClientOptions: policy.ClientOptions{Transport: tp}}
return NewOnBehalfOfCredentialWithCertificate(fakeTenantID, fakeClientID, userAssertion, certs, key, &o)
},
name: "certificate",
verifyCredential: func(t *testing.T, r *http.Request) {
require.NotEmpty(t, r.FormValue("client_assertion"))
},
},
{
ctor: func(tp policy.Transporter) (*OnBehalfOfCredential, error) {
o := OnBehalfOfCredentialOptions{ClientOptions: policy.ClientOptions{Transport: tp}, SendCertificateChain: true}
return NewOnBehalfOfCredentialWithCertificate(fakeTenantID, fakeClientID, expectedAssertion, certs, key, &o)
return NewOnBehalfOfCredentialWithCertificate(fakeTenantID, fakeClientID, userAssertion, certs, key, &o)
},
name: "SNI",
sendX5C: true,
verifyCredential: func(t *testing.T, r *http.Request) {
require.NotEmpty(t, r.FormValue("client_assertion"))
},
},
{
ctor: func(tp policy.Transporter) (*OnBehalfOfCredential, error) {
o := OnBehalfOfCredentialOptions{ClientOptions: policy.ClientOptions{Transport: tp}}
return NewOnBehalfOfCredentialWithSecret(fakeTenantID, fakeClientID, expectedAssertion, "secret", &o)
return NewOnBehalfOfCredentialWithSecret(fakeTenantID, fakeClientID, userAssertion, fakeSecret, &o)
},
name: "secret",
verifyCredential: func(t *testing.T, r *http.Request) {
require.Equal(t, fakeSecret, r.FormValue("client_secret"))
},
},
} {
t.Run(test.name, func(t *testing.T) {
Expand All @@ -63,12 +88,13 @@ func TestOnBehalfOfCredential(t *testing.T) {
if scope := r.FormValue("scope"); !strings.Contains(scope, liveTestScope) {
t.Errorf(`unexpected scopes "%v"`, scope)
}
if assertion := r.FormValue("assertion"); assertion != expectedAssertion {
t.Errorf(`unexpected assertion "%s"`, assertion)
if assertion := r.FormValue("assertion"); assertion != userAssertion {
t.Errorf(`unexpected user assertion "%s"`, assertion)
}
if test.sendX5C {
validateX5C(t, certs)(r)
}
test.verifyCredential(t, r)
return nil
}}
cred, err := test.ctor(&srv)
Expand Down