1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT license.
3
4/*
5Package confidential provides a client for authentication of "confidential" applications.
6A "confidential" application is defined as an app that run on servers. They are considered
7difficult to access and for that reason capable of keeping an application secret.
8Confidential clients can hold configuration-time secrets.
9*/
10package confidential
11
12import (
13 "context"
14 "crypto"
15 "crypto/rsa"
16 "crypto/x509"
17 "encoding/base64"
18 "encoding/pem"
19 "errors"
20 "fmt"
21
22 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache"
23 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base"
24 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/exported"
25 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth"
26 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops"
27 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens"
28 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority"
29 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/options"
30 "github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared"
31)
32
33/*
34Design note:
35
36confidential.Client uses base.Client as an embedded type. base.Client statically assigns its attributes
37during creation. As it doesn't have any pointers in it, anything borrowed from it, such as
38Base.AuthParams is a copy that is free to be manipulated here.
39
40Duplicate Calls shared between public.Client and this package:
41There is some duplicate call options provided here that are the same as in public.Client . This
42is a design choices. Go proverb(https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s):
43"a little copying is better than a little dependency". Yes, we could have another package with
44shared options (fail). That divides like 2 options from all others which makes the user look
45through more docs. We can have all clients in one package, but I think separate packages
46here makes for better naming (public.Client vs client.PublicClient). So I chose a little
47duplication.
48
49.Net People, Take note on X509:
50This uses x509.Certificates and private keys. x509 does not store private keys. .Net
51has a x509.Certificate2 abstraction that has private keys, but that just a strange invention.
52As such I've put a PEM decoder into here.
53*/
54
55// TODO(msal): This should have example code for each method on client using Go's example doc framework.
56// base usage details should be include in the package documentation.
57
58// AuthResult contains the results of one token acquisition operation.
59// For details see https://aka.ms/msal-net-authenticationresult
60type AuthResult = base.AuthResult
61
62type AuthenticationScheme = authority.AuthenticationScheme
63
64type Account = shared.Account
65
66// CertFromPEM converts a PEM file (.pem or .key) for use with [NewCredFromCert]. The file
67// must contain the public certificate and the private key. If a PEM block is encrypted and
68// password is not an empty string, it attempts to decrypt the PEM blocks using the password.
69// Multiple certs are due to certificate chaining for use cases like TLS that sign from root to leaf.
70func CertFromPEM(pemData []byte, password string) ([]*x509.Certificate, crypto.PrivateKey, error) {
71 var certs []*x509.Certificate
72 var priv crypto.PrivateKey
73 for {
74 block, rest := pem.Decode(pemData)
75 if block == nil {
76 break
77 }
78
79 //nolint:staticcheck // x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock are deprecated. They are used here only to support a usecase.
80 if x509.IsEncryptedPEMBlock(block) {
81 b, err := x509.DecryptPEMBlock(block, []byte(password))
82 if err != nil {
83 return nil, nil, fmt.Errorf("could not decrypt encrypted PEM block: %v", err)
84 }
85 block, _ = pem.Decode(b)
86 if block == nil {
87 return nil, nil, fmt.Errorf("encounter encrypted PEM block that did not decode")
88 }
89 }
90
91 switch block.Type {
92 case "CERTIFICATE":
93 cert, err := x509.ParseCertificate(block.Bytes)
94 if err != nil {
95 return nil, nil, fmt.Errorf("block labelled 'CERTIFICATE' could not be parsed by x509: %v", err)
96 }
97 certs = append(certs, cert)
98 case "PRIVATE KEY":
99 if priv != nil {
100 return nil, nil, errors.New("found multiple private key blocks")
101 }
102
103 var err error
104 priv, err = x509.ParsePKCS8PrivateKey(block.Bytes)
105 if err != nil {
106 return nil, nil, fmt.Errorf("could not decode private key: %v", err)
107 }
108 case "RSA PRIVATE KEY":
109 if priv != nil {
110 return nil, nil, errors.New("found multiple private key blocks")
111 }
112 var err error
113 priv, err = x509.ParsePKCS1PrivateKey(block.Bytes)
114 if err != nil {
115 return nil, nil, fmt.Errorf("could not decode private key: %v", err)
116 }
117 }
118 pemData = rest
119 }
120
121 if len(certs) == 0 {
122 return nil, nil, fmt.Errorf("no certificates found")
123 }
124
125 if priv == nil {
126 return nil, nil, fmt.Errorf("no private key found")
127 }
128
129 return certs, priv, nil
130}
131
132// AssertionRequestOptions has required information for client assertion claims
133type AssertionRequestOptions = exported.AssertionRequestOptions
134
135// Credential represents the credential used in confidential client flows.
136type Credential struct {
137 secret string
138
139 cert *x509.Certificate
140 key crypto.PrivateKey
141 x5c []string
142
143 assertionCallback func(context.Context, AssertionRequestOptions) (string, error)
144
145 tokenProvider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)
146}
147
148// toInternal returns the accesstokens.Credential that is used internally. The current structure of the
149// code requires that client.go, requests.go and confidential.go share a credential type without
150// having import recursion. That requires the type used between is in a shared package. Therefore
151// we have this.
152func (c Credential) toInternal() (*accesstokens.Credential, error) {
153 if c.secret != "" {
154 return &accesstokens.Credential{Secret: c.secret}, nil
155 }
156 if c.cert != nil {
157 if c.key == nil {
158 return nil, errors.New("missing private key for certificate")
159 }
160 return &accesstokens.Credential{Cert: c.cert, Key: c.key, X5c: c.x5c}, nil
161 }
162 if c.key != nil {
163 return nil, errors.New("missing certificate for private key")
164 }
165 if c.assertionCallback != nil {
166 return &accesstokens.Credential{AssertionCallback: c.assertionCallback}, nil
167 }
168 if c.tokenProvider != nil {
169 return &accesstokens.Credential{TokenProvider: c.tokenProvider}, nil
170 }
171 return nil, errors.New("invalid credential")
172}
173
174// NewCredFromSecret creates a Credential from a secret.
175func NewCredFromSecret(secret string) (Credential, error) {
176 if secret == "" {
177 return Credential{}, errors.New("secret can't be empty string")
178 }
179 return Credential{secret: secret}, nil
180}
181
182// NewCredFromAssertionCallback creates a Credential that invokes a callback to get assertions
183// authenticating the application. The callback must be thread safe.
184func NewCredFromAssertionCallback(callback func(context.Context, AssertionRequestOptions) (string, error)) Credential {
185 return Credential{assertionCallback: callback}
186}
187
188// NewCredFromCert creates a Credential from a certificate or chain of certificates and an RSA private key
189// as returned by [CertFromPEM].
190func NewCredFromCert(certs []*x509.Certificate, key crypto.PrivateKey) (Credential, error) {
191 cred := Credential{key: key}
192 k, ok := key.(*rsa.PrivateKey)
193 if !ok {
194 return cred, errors.New("key must be an RSA key")
195 }
196 for _, cert := range certs {
197 if cert == nil {
198 // not returning an error here because certs may still contain a sufficient cert/key pair
199 continue
200 }
201 certKey, ok := cert.PublicKey.(*rsa.PublicKey)
202 if ok && k.E == certKey.E && k.N.Cmp(certKey.N) == 0 {
203 // We know this is the signing cert because its public key matches the given private key.
204 // This cert must be first in x5c.
205 cred.cert = cert
206 cred.x5c = append([]string{base64.StdEncoding.EncodeToString(cert.Raw)}, cred.x5c...)
207 } else {
208 cred.x5c = append(cred.x5c, base64.StdEncoding.EncodeToString(cert.Raw))
209 }
210 }
211 if cred.cert == nil {
212 return cred, errors.New("key doesn't match any certificate")
213 }
214 return cred, nil
215}
216
217// TokenProviderParameters is the authentication parameters passed to token providers
218type TokenProviderParameters = exported.TokenProviderParameters
219
220// TokenProviderResult is the authentication result returned by custom token providers
221type TokenProviderResult = exported.TokenProviderResult
222
223// NewCredFromTokenProvider creates a Credential from a function that provides access tokens. The function
224// must be concurrency safe. This is intended only to allow the Azure SDK to cache MSI tokens. It isn't
225// useful to applications in general because the token provider must implement all authentication logic.
226func NewCredFromTokenProvider(provider func(context.Context, TokenProviderParameters) (TokenProviderResult, error)) Credential {
227 return Credential{tokenProvider: provider}
228}
229
230// AutoDetectRegion instructs MSAL Go to auto detect region for Azure regional token service.
231func AutoDetectRegion() string {
232 return "TryAutoDetect"
233}
234
235// Client is a representation of authentication client for confidential applications as defined in the
236// package doc. A new Client should be created PER SERVICE USER.
237// For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications
238type Client struct {
239 base base.Client
240 cred *accesstokens.Credential
241}
242
243// clientOptions are optional settings for New(). These options are set using various functions
244// returning Option calls.
245type clientOptions struct {
246 accessor cache.ExportReplace
247 authority, azureRegion string
248 capabilities []string
249 disableInstanceDiscovery, sendX5C bool
250 httpClient ops.HTTPClient
251}
252
253// Option is an optional argument to New().
254type Option func(o *clientOptions)
255
256// WithCache provides an accessor that will read and write authentication data to an externally managed cache.
257func WithCache(accessor cache.ExportReplace) Option {
258 return func(o *clientOptions) {
259 o.accessor = accessor
260 }
261}
262
263// WithClientCapabilities allows configuring one or more client capabilities such as "CP1"
264func WithClientCapabilities(capabilities []string) Option {
265 return func(o *clientOptions) {
266 // there's no danger of sharing the slice's underlying memory with the application because
267 // this slice is simply passed to base.WithClientCapabilities, which copies its data
268 o.capabilities = capabilities
269 }
270}
271
272// WithHTTPClient allows for a custom HTTP client to be set.
273func WithHTTPClient(httpClient ops.HTTPClient) Option {
274 return func(o *clientOptions) {
275 o.httpClient = httpClient
276 }
277}
278
279// WithX5C specifies if x5c claim(public key of the certificate) should be sent to STS to enable Subject Name Issuer Authentication.
280func WithX5C() Option {
281 return func(o *clientOptions) {
282 o.sendX5C = true
283 }
284}
285
286// WithInstanceDiscovery set to false to disable authority validation (to support private cloud scenarios)
287func WithInstanceDiscovery(enabled bool) Option {
288 return func(o *clientOptions) {
289 o.disableInstanceDiscovery = !enabled
290 }
291}
292
293// WithAzureRegion sets the region(preferred) or Confidential.AutoDetectRegion() for auto detecting region.
294// Region names as per https://azure.microsoft.com/en-ca/global-infrastructure/geographies/.
295// See https://aka.ms/region-map for more details on region names.
296// The region value should be short region name for the region where the service is deployed.
297// For example "centralus" is short name for region Central US.
298// Not all auth flows can use the regional token service.
299// Service To Service (client credential flow) tokens can be obtained from the regional service.
300// Requires configuration at the tenant level.
301// Auto-detection works on a limited number of Azure artifacts (VMs, Azure functions).
302// If auto-detection fails, the non-regional endpoint will be used.
303// If an invalid region name is provided, the non-regional endpoint MIGHT be used or the token request MIGHT fail.
304func WithAzureRegion(val string) Option {
305 return func(o *clientOptions) {
306 o.azureRegion = val
307 }
308}
309
310// New is the constructor for Client. authority is the URL of a token authority such as "https://login.microsoftonline.com/<your tenant>".
311// If the Client will connect directly to AD FS, use "adfs" for the tenant. clientID is the application's client ID (also called its
312// "application ID").
313func New(authority, clientID string, cred Credential, options ...Option) (Client, error) {
314 internalCred, err := cred.toInternal()
315 if err != nil {
316 return Client{}, err
317 }
318
319 opts := clientOptions{
320 authority: authority,
321 // if the caller specified a token provider, it will handle all details of authentication, using Client only as a token cache
322 disableInstanceDiscovery: cred.tokenProvider != nil,
323 httpClient: shared.DefaultClient,
324 }
325 for _, o := range options {
326 o(&opts)
327 }
328 baseOpts := []base.Option{
329 base.WithCacheAccessor(opts.accessor),
330 base.WithClientCapabilities(opts.capabilities),
331 base.WithInstanceDiscovery(!opts.disableInstanceDiscovery),
332 base.WithRegionDetection(opts.azureRegion),
333 base.WithX5C(opts.sendX5C),
334 }
335 base, err := base.New(clientID, opts.authority, oauth.New(opts.httpClient), baseOpts...)
336 if err != nil {
337 return Client{}, err
338 }
339 base.AuthParams.IsConfidentialClient = true
340
341 return Client{base: base, cred: internalCred}, nil
342}
343
344// authCodeURLOptions contains options for AuthCodeURL
345type authCodeURLOptions struct {
346 claims, loginHint, tenantID, domainHint string
347}
348
349// AuthCodeURLOption is implemented by options for AuthCodeURL
350type AuthCodeURLOption interface {
351 authCodeURLOption()
352}
353
354// AuthCodeURL creates a URL used to acquire an authorization code. Users need to call CreateAuthorizationCodeURLParameters and pass it in.
355//
356// Options: [WithClaims], [WithDomainHint], [WithLoginHint], [WithTenantID]
357func (cca Client) AuthCodeURL(ctx context.Context, clientID, redirectURI string, scopes []string, opts ...AuthCodeURLOption) (string, error) {
358 o := authCodeURLOptions{}
359 if err := options.ApplyOptions(&o, opts); err != nil {
360 return "", err
361 }
362 ap, err := cca.base.AuthParams.WithTenant(o.tenantID)
363 if err != nil {
364 return "", err
365 }
366 ap.Claims = o.claims
367 ap.LoginHint = o.loginHint
368 ap.DomainHint = o.domainHint
369 return cca.base.AuthCodeURL(ctx, clientID, redirectURI, scopes, ap)
370}
371
372// WithLoginHint pre-populates the login prompt with a username.
373func WithLoginHint(username string) interface {
374 AuthCodeURLOption
375 options.CallOption
376} {
377 return struct {
378 AuthCodeURLOption
379 options.CallOption
380 }{
381 CallOption: options.NewCallOption(
382 func(a any) error {
383 switch t := a.(type) {
384 case *authCodeURLOptions:
385 t.loginHint = username
386 default:
387 return fmt.Errorf("unexpected options type %T", a)
388 }
389 return nil
390 },
391 ),
392 }
393}
394
395// WithDomainHint adds the IdP domain as domain_hint query parameter in the auth url.
396func WithDomainHint(domain string) interface {
397 AuthCodeURLOption
398 options.CallOption
399} {
400 return struct {
401 AuthCodeURLOption
402 options.CallOption
403 }{
404 CallOption: options.NewCallOption(
405 func(a any) error {
406 switch t := a.(type) {
407 case *authCodeURLOptions:
408 t.domainHint = domain
409 default:
410 return fmt.Errorf("unexpected options type %T", a)
411 }
412 return nil
413 },
414 ),
415 }
416}
417
418// WithClaims sets additional claims to request for the token, such as those required by conditional access policies.
419// Use this option when Azure AD returned a claims challenge for a prior request. The argument must be decoded.
420// This option is valid for any token acquisition method.
421func WithClaims(claims string) interface {
422 AcquireByAuthCodeOption
423 AcquireByCredentialOption
424 AcquireOnBehalfOfOption
425 AcquireSilentOption
426 AuthCodeURLOption
427 options.CallOption
428} {
429 return struct {
430 AcquireByAuthCodeOption
431 AcquireByCredentialOption
432 AcquireOnBehalfOfOption
433 AcquireSilentOption
434 AuthCodeURLOption
435 options.CallOption
436 }{
437 CallOption: options.NewCallOption(
438 func(a any) error {
439 switch t := a.(type) {
440 case *acquireTokenByAuthCodeOptions:
441 t.claims = claims
442 case *acquireTokenByCredentialOptions:
443 t.claims = claims
444 case *acquireTokenOnBehalfOfOptions:
445 t.claims = claims
446 case *acquireTokenSilentOptions:
447 t.claims = claims
448 case *authCodeURLOptions:
449 t.claims = claims
450 default:
451 return fmt.Errorf("unexpected options type %T", a)
452 }
453 return nil
454 },
455 ),
456 }
457}
458
459// WithAuthenticationScheme is an extensibility mechanism designed to be used only by Azure Arc for proof of possession access tokens.
460func WithAuthenticationScheme(authnScheme AuthenticationScheme) interface {
461 AcquireSilentOption
462 AcquireByCredentialOption
463 options.CallOption
464} {
465 return struct {
466 AcquireSilentOption
467 AcquireByCredentialOption
468 options.CallOption
469 }{
470 CallOption: options.NewCallOption(
471 func(a any) error {
472 switch t := a.(type) {
473 case *acquireTokenSilentOptions:
474 t.authnScheme = authnScheme
475 case *acquireTokenByCredentialOptions:
476 t.authnScheme = authnScheme
477 default:
478 return fmt.Errorf("unexpected options type %T", a)
479 }
480 return nil
481 },
482 ),
483 }
484}
485
486// WithTenantID specifies a tenant for a single authentication. It may be different than the tenant set in [New].
487// This option is valid for any token acquisition method.
488func WithTenantID(tenantID string) interface {
489 AcquireByAuthCodeOption
490 AcquireByCredentialOption
491 AcquireOnBehalfOfOption
492 AcquireSilentOption
493 AuthCodeURLOption
494 options.CallOption
495} {
496 return struct {
497 AcquireByAuthCodeOption
498 AcquireByCredentialOption
499 AcquireOnBehalfOfOption
500 AcquireSilentOption
501 AuthCodeURLOption
502 options.CallOption
503 }{
504 CallOption: options.NewCallOption(
505 func(a any) error {
506 switch t := a.(type) {
507 case *acquireTokenByAuthCodeOptions:
508 t.tenantID = tenantID
509 case *acquireTokenByCredentialOptions:
510 t.tenantID = tenantID
511 case *acquireTokenOnBehalfOfOptions:
512 t.tenantID = tenantID
513 case *acquireTokenSilentOptions:
514 t.tenantID = tenantID
515 case *authCodeURLOptions:
516 t.tenantID = tenantID
517 default:
518 return fmt.Errorf("unexpected options type %T", a)
519 }
520 return nil
521 },
522 ),
523 }
524}
525
526// acquireTokenSilentOptions are all the optional settings to an AcquireTokenSilent() call.
527// These are set by using various AcquireTokenSilentOption functions.
528type acquireTokenSilentOptions struct {
529 account Account
530 claims, tenantID string
531 authnScheme AuthenticationScheme
532}
533
534// AcquireSilentOption is implemented by options for AcquireTokenSilent
535type AcquireSilentOption interface {
536 acquireSilentOption()
537}
538
539// WithSilentAccount uses the passed account during an AcquireTokenSilent() call.
540func WithSilentAccount(account Account) interface {
541 AcquireSilentOption
542 options.CallOption
543} {
544 return struct {
545 AcquireSilentOption
546 options.CallOption
547 }{
548 CallOption: options.NewCallOption(
549 func(a any) error {
550 switch t := a.(type) {
551 case *acquireTokenSilentOptions:
552 t.account = account
553 default:
554 return fmt.Errorf("unexpected options type %T", a)
555 }
556 return nil
557 },
558 ),
559 }
560}
561
562// AcquireTokenSilent acquires a token from either the cache or using a refresh token.
563//
564// Options: [WithClaims], [WithSilentAccount], [WithTenantID]
565func (cca Client) AcquireTokenSilent(ctx context.Context, scopes []string, opts ...AcquireSilentOption) (AuthResult, error) {
566 o := acquireTokenSilentOptions{}
567 if err := options.ApplyOptions(&o, opts); err != nil {
568 return AuthResult{}, err
569 }
570
571 if o.claims != "" {
572 return AuthResult{}, errors.New("call another AcquireToken method to request a new token having these claims")
573 }
574
575 silentParameters := base.AcquireTokenSilentParameters{
576 Scopes: scopes,
577 Account: o.account,
578 RequestType: accesstokens.ATConfidential,
579 Credential: cca.cred,
580 IsAppCache: o.account.IsZero(),
581 TenantID: o.tenantID,
582 AuthnScheme: o.authnScheme,
583 }
584
585 return cca.base.AcquireTokenSilent(ctx, silentParameters)
586}
587
588// acquireTokenByAuthCodeOptions contains the optional parameters used to acquire an access token using the authorization code flow.
589type acquireTokenByAuthCodeOptions struct {
590 challenge, claims, tenantID string
591}
592
593// AcquireByAuthCodeOption is implemented by options for AcquireTokenByAuthCode
594type AcquireByAuthCodeOption interface {
595 acquireByAuthCodeOption()
596}
597
598// WithChallenge allows you to provide a challenge for the .AcquireTokenByAuthCode() call.
599func WithChallenge(challenge string) interface {
600 AcquireByAuthCodeOption
601 options.CallOption
602} {
603 return struct {
604 AcquireByAuthCodeOption
605 options.CallOption
606 }{
607 CallOption: options.NewCallOption(
608 func(a any) error {
609 switch t := a.(type) {
610 case *acquireTokenByAuthCodeOptions:
611 t.challenge = challenge
612 default:
613 return fmt.Errorf("unexpected options type %T", a)
614 }
615 return nil
616 },
617 ),
618 }
619}
620
621// AcquireTokenByAuthCode is a request to acquire a security token from the authority, using an authorization code.
622// The specified redirect URI must be the same URI that was used when the authorization code was requested.
623//
624// Options: [WithChallenge], [WithClaims], [WithTenantID]
625func (cca Client) AcquireTokenByAuthCode(ctx context.Context, code string, redirectURI string, scopes []string, opts ...AcquireByAuthCodeOption) (AuthResult, error) {
626 o := acquireTokenByAuthCodeOptions{}
627 if err := options.ApplyOptions(&o, opts); err != nil {
628 return AuthResult{}, err
629 }
630
631 params := base.AcquireTokenAuthCodeParameters{
632 Scopes: scopes,
633 Code: code,
634 Challenge: o.challenge,
635 Claims: o.claims,
636 AppType: accesstokens.ATConfidential,
637 Credential: cca.cred, // This setting differs from public.Client.AcquireTokenByAuthCode
638 RedirectURI: redirectURI,
639 TenantID: o.tenantID,
640 }
641
642 return cca.base.AcquireTokenByAuthCode(ctx, params)
643}
644
645// acquireTokenByCredentialOptions contains optional configuration for AcquireTokenByCredential
646type acquireTokenByCredentialOptions struct {
647 claims, tenantID string
648 authnScheme AuthenticationScheme
649}
650
651// AcquireByCredentialOption is implemented by options for AcquireTokenByCredential
652type AcquireByCredentialOption interface {
653 acquireByCredOption()
654}
655
656// AcquireTokenByCredential acquires a security token from the authority, using the client credentials grant.
657//
658// Options: [WithClaims], [WithTenantID]
659func (cca Client) AcquireTokenByCredential(ctx context.Context, scopes []string, opts ...AcquireByCredentialOption) (AuthResult, error) {
660 o := acquireTokenByCredentialOptions{}
661 err := options.ApplyOptions(&o, opts)
662 if err != nil {
663 return AuthResult{}, err
664 }
665 authParams, err := cca.base.AuthParams.WithTenant(o.tenantID)
666 if err != nil {
667 return AuthResult{}, err
668 }
669 authParams.Scopes = scopes
670 authParams.AuthorizationType = authority.ATClientCredentials
671 authParams.Claims = o.claims
672 if o.authnScheme != nil {
673 authParams.AuthnScheme = o.authnScheme
674 }
675 token, err := cca.base.Token.Credential(ctx, authParams, cca.cred)
676 if err != nil {
677 return AuthResult{}, err
678 }
679 return cca.base.AuthResultFromToken(ctx, authParams, token, true)
680}
681
682// acquireTokenOnBehalfOfOptions contains optional configuration for AcquireTokenOnBehalfOf
683type acquireTokenOnBehalfOfOptions struct {
684 claims, tenantID string
685}
686
687// AcquireOnBehalfOfOption is implemented by options for AcquireTokenOnBehalfOf
688type AcquireOnBehalfOfOption interface {
689 acquireOBOOption()
690}
691
692// AcquireTokenOnBehalfOf acquires a security token for an app using middle tier apps access token.
693// Refer https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.
694//
695// Options: [WithClaims], [WithTenantID]
696func (cca Client) AcquireTokenOnBehalfOf(ctx context.Context, userAssertion string, scopes []string, opts ...AcquireOnBehalfOfOption) (AuthResult, error) {
697 o := acquireTokenOnBehalfOfOptions{}
698 if err := options.ApplyOptions(&o, opts); err != nil {
699 return AuthResult{}, err
700 }
701 params := base.AcquireTokenOnBehalfOfParameters{
702 Scopes: scopes,
703 UserAssertion: userAssertion,
704 Claims: o.claims,
705 Credential: cca.cred,
706 TenantID: o.tenantID,
707 }
708 return cca.base.AcquireTokenOnBehalfOf(ctx, params)
709}
710
711// Account gets the account in the token cache with the specified homeAccountID.
712func (cca Client) Account(ctx context.Context, accountID string) (Account, error) {
713 return cca.base.Account(ctx, accountID)
714}
715
716// RemoveAccount signs the account out and forgets account from token cache.
717func (cca Client) RemoveAccount(ctx context.Context, account Account) error {
718 return cca.base.RemoveAccount(ctx, account)
719}