Skip to content
Merged
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
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
14 changes: 14 additions & 0 deletions sdk/azidentity/on_behalf_of_credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"crypto"
"crypto/x509"
"errors"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
Expand Down Expand Up @@ -60,6 +61,19 @@ 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) {
if getAssertion == nil {
return nil, errors.New("getAssertion can't be nil. It must be a function that returns client assertions")
}
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