身份验证成功后,命令行应用通过后端通道接收所需的令牌,并使用它们来执行所需的 Web API 调用。
macOS
Node.js
Python
IPublicClientApplication
包含名为
AcquireTokenWithDeviceCode
的方法。
AcquireTokenWithDeviceCode(IEnumerable<string> scopes,
Func<DeviceCodeResult, Task> deviceCodeResultCallback)
该方法用作参数:
要请求其访问令牌的 scopes
。
接收 DeviceCodeResult
的回调。
以下示例代码显示了最新事例的概要,并解释了可能出现的各种异常及其缓解措施。 有关完整的功能代码示例,请参阅 GitHub 上的 active-directory-dotnetcore-devicecodeflow-v2。
private const string ClientId = "<client_guid>";
private const string Authority = "https://login.microsoftonline.com/contoso.com";
private readonly string[] scopes = new string[] { "user.read" };
static async Task<AuthenticationResult> GetATokenForGraph()
IPublicClientApplication pca = PublicClientApplicationBuilder
.Create(ClientId)
.WithAuthority(Authority)
.WithDefaultRedirectUri()
.Build();
var accounts = await pca.GetAccountsAsync();
// All AcquireToken* methods store the tokens in the cache, so check the cache first
return await pca.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
catch (MsalUiRequiredException ex)
// No token found in the cache or Azure AD insists that a form interactive auth is required (e.g. the tenant admin turned on MFA)
// If you want to provide a more complex user experience, check out ex.Classification
return await AcquireByDeviceCodeAsync(pca);
private static async Task<AuthenticationResult> AcquireByDeviceCodeAsync(IPublicClientApplication pca)
var result = await pca.AcquireTokenWithDeviceCode(scopes,
deviceCodeResult =>
// This will print the message on the console which tells the user where to go sign-in using
// a separate browser and the code to enter once they sign in.
// The AcquireTokenWithDeviceCode() method will poll the server after firing this
// device code callback to look for the successful login of the user via that browser.
// This background polling (whose interval and timeout data is also provided as fields in the
// deviceCodeCallback class) will occur until:
// * The user has successfully logged in via browser and entered the proper code
// * The timeout specified by the server for the lifetime of this code (typically ~15 minutes) has been reached
// * The developing application calls the Cancel() method on a CancellationToken sent into the method.
// If this occurs, an OperationCanceledException will be thrown (see catch below for more details).
Console.WriteLine(deviceCodeResult.Message);
return Task.FromResult(0);
}).ExecuteAsync();
Console.WriteLine(result.Account.Username);
return result;
// TODO: handle or throw all these exceptions depending on your app
catch (MsalServiceException ex)
// Kind of errors you could have (in ex.Message)
// AADSTS50059: No tenant-identifying information found in either the request or implied by any provided credentials.
// Mitigation: as explained in the message from Azure AD, the authoriy needs to be tenanted. you have probably created
// your public client application with the following authorities:
// https://login.microsoftonline.com/common or https://login.microsoftonline.com/organizations
// AADSTS90133: Device Code flow is not supported under /common or /consumers endpoint.
// Mitigation: as explained in the message from Azure AD, the authority needs to be tenanted
// AADSTS90002: Tenant <tenantId or domain you used in the authority> not found. This may happen if there are
// no active subscriptions for the tenant. Check with your subscription administrator.
// Mitigation: if you have an active subscription for the tenant this might be that you have a typo in the
// tenantId (GUID) or tenant domain name.
catch (OperationCanceledException ex)
// If you use a CancellationToken, and call the Cancel() method on it, then this *may* be triggered
// to indicate that the operation was cancelled.
// See https://learn.microsoft.com/dotnet/standard/threading/cancellation-in-managed-threads
// for more detailed information on how C# supports cancellation in managed threads.
catch (MsalClientException ex)
// Possible cause - verification code expired before contacting the server
// This exception will occur if the user does not manage to sign-in before a time out (15 mins) and the
// call to `AcquireTokenWithDeviceCode` is not cancelled in between
此代码摘录自 MSAL Java 代码示例。
private static IAuthenticationResult acquireTokenDeviceCode() throws Exception {
// Load token cache from file and initialize token cache aspect. The token cache will have
// dummy data, so the acquireTokenSilently call will fail.
TokenCacheAspect tokenCacheAspect = new TokenCacheAspect("sample_cache.json");
PublicClientApplication pca = PublicClientApplication.builder(CLIENT_ID)
.authority(AUTHORITY)
.setTokenCacheAccessAspect(tokenCacheAspect)
.build();
Set<IAccount> accountsInCache = pca.getAccounts().join();
// Take first account in the cache. In a production application, you would filter
// accountsInCache to get the right account for the user authenticating.
IAccount account = accountsInCache.iterator().next();
IAuthenticationResult result;
try {
SilentParameters silentParameters =
SilentParameters
.builder(SCOPE, account)
.build();
// try to acquire token silently. This call will fail since the token cache
// does not have any data for the user you are trying to acquire a token for
result = pca.acquireTokenSilently(silentParameters).join();
} catch (Exception ex) {
if (ex.getCause() instanceof MsalException) {
Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) ->
System.out.println(deviceCode.message());
DeviceCodeFlowParameters parameters =
DeviceCodeFlowParameters
.builder(SCOPE, deviceCodeConsumer)
.build();
// Try to acquire a token via device code flow. If successful, you should see
// the token and account information printed out to console, and the sample_cache.json
// file should have been updated with the latest tokens.
result = pca.acquireToken(parameters).join();
} else {
// Handle other exceptions accordingly
throw ex;
return result;
const deviceCodeRequest = {
deviceCodeCallback: (response) => (console.log(response.message)),
scopes: ["user.read"],
timeout: 20,
pca.acquireTokenByDeviceCode(deviceCodeRequest).then((response) => {
console.log(JSON.stringify(response));
}).catch((error) => {
console.log(JSON.stringify(error));
此代码摘录自 MSAL Python 开发示例。
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
# token_cache=... # Default cache is in memory only.
# You can learn how to use SerializableTokenCache from
# https://msal-python.rtfd.io/en/latest/#msal.SerializableTokenCache
# The pattern to acquire a token looks like this.
result = None
# Note: If your device-flow app does not have any interactive ability, you can
# completely skip the following cache part. But here we demonstrate it anyway.
# We now check the cache to see if we have some end users signed in before.
accounts = app.get_accounts()
if accounts:
logging.info("Account(s) exists in cache, probably with token too. Let's try.")
print("Pick the account you want to use to proceed:")
for a in accounts:
print(a["username"])
# Assuming the end user chose this one
chosen = accounts[0]
# Now let's try to find a token in cache for this account
result = app.acquire_token_silent(config["scope"], account=chosen)
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from Azure AD.")
flow = app.initiate_device_flow(scopes=config["scope"])
if "user_code" not in flow:
raise ValueError(
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))
print(flow["message"])
sys.stdout.flush() # Some terminal needs this to ensure the message is shown
# Ideally you should wait here, in order to save some unnecessary polling
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")
result = app.acquire_token_by_device_flow(flow) # By default it will block
# You can follow this instruction to shorten the block time
# https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow
# or you may even turn off the blocking behavior,
# and then keep calling acquire_token_by_device_flow(flow) in your own customized loop
转到此方案中的下一篇文章:从桌面应用调用 Web API。