1//go:build go1.18
2// +build go1.18
3
4// Copyright (c) Microsoft Corporation. All rights reserved.
5// Licensed under the MIT License.
6
7package azidentity
8
9import (
10 "context"
11 "fmt"
12
13 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
14 "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
15 "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
16)
17
18const credNameDeviceCode = "DeviceCodeCredential"
19
20// DeviceCodeCredentialOptions contains optional parameters for DeviceCodeCredential.
21type DeviceCodeCredentialOptions struct {
22 azcore.ClientOptions
23
24 // AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire
25 // tokens. Add the wildcard value "*" to allow the credential to acquire tokens for any tenant.
26 AdditionallyAllowedTenants []string
27
28 // authenticationRecord returned by a call to a credential's Authenticate method. Set this option
29 // to enable the credential to use data from a previous authentication.
30 authenticationRecord authenticationRecord
31
32 // ClientID is the ID of the application users will authenticate to.
33 // Defaults to the ID of an Azure development application.
34 ClientID string
35
36 // disableAutomaticAuthentication prevents the credential from automatically prompting the user to authenticate.
37 // When this option is true, GetToken will return authenticationRequiredError when user interaction is necessary
38 // to acquire a token.
39 disableAutomaticAuthentication bool
40
41 // DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
42 // private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
43 // from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
44 // the application responsible for ensuring the configured authority is valid and trustworthy.
45 DisableInstanceDiscovery bool
46
47 // TenantID is the Microsoft Entra tenant the credential authenticates in. Defaults to the
48 // "organizations" tenant, which can authenticate work and school accounts. Required for single-tenant
49 // applications.
50 TenantID string
51
52 // tokenCachePersistenceOptions enables persistent token caching when not nil.
53 tokenCachePersistenceOptions *tokenCachePersistenceOptions
54
55 // UserPrompt controls how the credential presents authentication instructions. The credential calls
56 // this function with authentication details when it receives a device code. By default, the credential
57 // prints these details to stdout.
58 UserPrompt func(context.Context, DeviceCodeMessage) error
59}
60
61func (o *DeviceCodeCredentialOptions) init() {
62 if o.TenantID == "" {
63 o.TenantID = organizationsTenantID
64 }
65 if o.ClientID == "" {
66 o.ClientID = developerSignOnClientID
67 }
68 if o.UserPrompt == nil {
69 o.UserPrompt = func(ctx context.Context, dc DeviceCodeMessage) error {
70 fmt.Println(dc.Message)
71 return nil
72 }
73 }
74}
75
76// DeviceCodeMessage contains the information a user needs to complete authentication.
77type DeviceCodeMessage struct {
78 // UserCode is the user code returned by the service.
79 UserCode string `json:"user_code"`
80 // VerificationURL is the URL at which the user must authenticate.
81 VerificationURL string `json:"verification_uri"`
82 // Message is user instruction from Microsoft Entra ID.
83 Message string `json:"message"`
84}
85
86// DeviceCodeCredential acquires tokens for a user via the device code flow, which has the
87// user browse to a Microsoft Entra URL, enter a code, and authenticate. It's useful
88// for authenticating a user in an environment without a web browser, such as an SSH session.
89// If a web browser is available, [InteractiveBrowserCredential] is more convenient because it
90// automatically opens a browser to the login page.
91type DeviceCodeCredential struct {
92 client *publicClient
93}
94
95// NewDeviceCodeCredential creates a DeviceCodeCredential. Pass nil to accept default options.
96func NewDeviceCodeCredential(options *DeviceCodeCredentialOptions) (*DeviceCodeCredential, error) {
97 cp := DeviceCodeCredentialOptions{}
98 if options != nil {
99 cp = *options
100 }
101 cp.init()
102 msalOpts := publicClientOptions{
103 AdditionallyAllowedTenants: cp.AdditionallyAllowedTenants,
104 ClientOptions: cp.ClientOptions,
105 DeviceCodePrompt: cp.UserPrompt,
106 DisableAutomaticAuthentication: cp.disableAutomaticAuthentication,
107 DisableInstanceDiscovery: cp.DisableInstanceDiscovery,
108 Record: cp.authenticationRecord,
109 TokenCachePersistenceOptions: cp.tokenCachePersistenceOptions,
110 }
111 c, err := newPublicClient(cp.TenantID, cp.ClientID, credNameDeviceCode, msalOpts)
112 if err != nil {
113 return nil, err
114 }
115 c.name = credNameDeviceCode
116 return &DeviceCodeCredential{client: c}, nil
117}
118
119// Authenticate a user via the device code flow. Subsequent calls to GetToken will automatically use the returned AuthenticationRecord.
120func (c *DeviceCodeCredential) authenticate(ctx context.Context, opts *policy.TokenRequestOptions) (authenticationRecord, error) {
121 var err error
122 ctx, endSpan := runtime.StartSpan(ctx, credNameDeviceCode+"."+traceOpAuthenticate, c.client.azClient.Tracer(), nil)
123 defer func() { endSpan(err) }()
124 tk, err := c.client.Authenticate(ctx, opts)
125 return tk, err
126}
127
128// GetToken requests an access token from Microsoft Entra ID. It will begin the device code flow and poll until the user completes authentication.
129// This method is called automatically by Azure SDK clients.
130func (c *DeviceCodeCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
131 var err error
132 ctx, endSpan := runtime.StartSpan(ctx, credNameDeviceCode+"."+traceOpGetToken, c.client.azClient.Tracer(), nil)
133 defer func() { endSpan(err) }()
134 tk, err := c.client.GetToken(ctx, opts)
135 return tk, err
136}
137
138var _ azcore.TokenCredential = (*DeviceCodeCredential)(nil)