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