驗證 Microsoft 帳號憑證程式碼範例
重要事項
從 2022 年 6 月 1 日開始,所有透過使用 Bing 廣告 API、內容 API 和旅館 API 的協力廠商應用程式登入的使用者,都必須進行多重要素驗證。
您必須更新應用程式,才能使用新 msads.manage
範圍取得使用者同意。 所有應用程式開發人員都必須採取動作,才能使用新的範圍。
如需詳細資訊,請參閱 Multi-Factor Authentication 需求 指南。
如需使用 OAuth 驗證 Microsoft Advertising 使用者的相關資訊,請參閱 使用 OAuth 進行驗證。 如果您目前使用適用于 .NET 的 Bing 廣告 SDK 來驗證使用者的 Microsoft 帳號憑證,您可以繼續這麼做。
此範例示範如何取得用來設定 AuthenticationToken 標頭的 OAuth 存取權杖。 此範例分成兩個部分。 第一個部分顯示簡單的用戶端,它會呼叫 第二個部分 ,以實作程式碼授與流程程式。
第一部分:用戶端
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using Content.OAuth; // Reference to CodeGrantFlow DLL
using Newtonsoft.Json; // NuGet Json.NET
namespace Content
{
class Program
{
// The application ID that you were given when you
// registered your application. This is for a
// desktop app so there's not client secret.
private static string _clientId = "<clientidgoeshere>";
// If _storedRefreshToken is null, CodeGrantFlow goes
// through the entire process of getting the user credentials
// and permissions. If _storedRefreshToken contains the refresh
// token, CodeGrantFlow returns the new access and refresh tokens.
private static string _storedRefreshToken = null;
private static CodeGrantOauth _tokens = null;
private static DateTime _tokenExpiration;
static void Main(string[] args)
{
try
{
// TODO: Add logic to get the logged on user's refresh token
// from secured storage.
_tokens = GetOauthTokens(_storedRefreshToken, _clientId);
Console.WriteLine("access token:" + _tokens.AccessToken.Substring(0, 15) + "...");
Console.WriteLine("refresh token: " + _tokens.RefreshToken.Substring(0, 15) + "...");
Console.WriteLine("token expires: " + _tokens.Expiration);
}
catch (Exception e)
{
Console.WriteLine("\n" + e.Message);
}
}
// Gets the OAuth tokens. If the refresh token doesn't exist, get
// the user's consent and a new access and refresh token.
private static CodeGrantOauth GetOauthTokens(string refreshToken, string clientId)
{
CodeGrantOauth auth = new CodeGrantOauth(clientId);
if (string.IsNullOrEmpty(refreshToken))
{
auth.GetAccessToken();
}
else
{
auth.RefreshAccessToken(refreshToken);
// Refresh tokens can become invalid for several reasons
// such as the user's password changed.
if (!string.IsNullOrEmpty(auth.Error))
{
auth = GetOauthTokens(null, clientId);
}
}
// TODO: Store the new refresh token in secured storage
// for the logged on user.
if (!string.IsNullOrEmpty(auth.Error))
{
throw new Exception(auth.Error);
}
else
{
_storedRefreshToken = auth.RefreshToken;
_tokenExpiration = DateTime.Now.AddSeconds(auth.Expiration);
}
return auth;
}
}
}
第二部分:程式碼授與流程實作
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Windows.Forms; // Add reference
using System.Net; // .Net 4.5 required for WebUtility.UrlEncode
using System.Drawing; // Add reference
using Newtonsoft.Json; // NuGet Json.NET
namespace Content.OAuth
{
// This class supports the OAuth 2.0 Authorization Code Grant flow
// for a desktop app.
public class CodeGrantOauth : Form
{
private WebBrowser _browser = null; // Used to get user consent
private string _accessToken = null;
private string _refreshToken = null;
private string _authorizationCode = null;
private int _expiration;
private string _error = null;
// Production OAuth server endpoints.
private string AuthorizationUri = "https://login.live.com/oauth20_authorize.srf"; // Authorization code endpoint
private string RedirectUri = "https://login.live.com/oauth20_desktop.srf"; // Callback endpoint
private string RefreshUri = "https://login.live.com/oauth20_token.srf"; // Get tokens endpoint
private string RedirectPath = "/oauth20_desktop.srf";
private string ErrorPath = "/err.srf";
// Parameters to pass to requests.
// codeQueryString is the query string for the authorizationUri. To force user log in, include the &prompt=login parameter.
// accessBody is the request body used with the refreshUri to get the access token using the authorization code.
// refreshBody is the request body used with the refreshUri to get the access token using a refresh token.
private string CodeQueryString = "?client_id={0}&scope=bingads.manage&response_type=code&redirect_uri={1}";
private string AccessBody = "client_id={0}&code={1}&grant_type=authorization_code&redirect_uri={2}";
private string RefreshBody = "client_id={0}&grant_type=refresh_token&redirect_uri={1}&refresh_token={2}";
private string _clientId = null;
private string _uri = null;
public string AccessToken { get { return this._accessToken; } }
public string RefreshToken { get { return this._refreshToken; } }
public int Expiration { get { return this._expiration; } }
public string Error { get { return this._error; } }
// Must instantiate the class by passing the apps client ID.
public CodeGrantOauth(string clientId)
{
if (string.IsNullOrEmpty(clientId))
{
throw new ArgumentException("The client ID is missing.");
}
this._clientId = clientId;
this._uri = string.Format(this.AuthorizationUri + this.CodeQueryString, this._clientId, RedirectUri);
}
// Add the browser to the form.
private void InitializeForm()
{
this.FormClosing += new FormClosingEventHandler(form_FormClosing);
this.Size = new Size(420, 580);
this._browser = new WebBrowser();
this._browser.Dock = DockStyle.Fill;
this._browser.Navigated += new WebBrowserNavigatedEventHandler(browser_Navigated);
this.Controls.AddRange(new Control[] { this._browser });
}
// Get the access token by using the authorization code.
public string GetAccessToken()
{
Thread oauthThread = new Thread(new ThreadStart(GetToken));
oauthThread.SetApartmentState(ApartmentState.STA);
oauthThread.Start();
oauthThread.Join();
try
{
if (!string.IsNullOrEmpty(this._authorizationCode))
{
var accessTokenRequestBody = string.Format(this.AccessBody, this._clientId, this._authorizationCode, WebUtility.UrlEncode(RedirectUri));
AccessTokens tokens = GetTokens(this.RefreshUri, accessTokenRequestBody);
this._accessToken = tokens.AccessToken;
this._refreshToken = tokens.RefreshToken;
this._expiration = tokens.Expiration;
}
}
catch (WebException)
{
this._error = "GetAccessToken failed likely due to an invalid client ID, client secret, or authorization code";
}
return this._accessToken;
}
// Starts the browser used to get consent and blocks until
// the user gives consent or cancels the request.
private void GetToken()
{
InitializeForm();
this._browser.Navigate(this._uri);
Application.EnableVisualStyles();
Application.Run(this);
}
// Get the access token by using the refresh token.
public string RefreshAccessToken(string refreshToken)
{
if (string.IsNullOrEmpty(refreshToken))
{
throw new ArgumentException("The refresh token is missing.");
}
try
{
var refreshTokenRequestBody = string.Format(this.RefreshBody, this._clientId, WebUtility.UrlEncode(RedirectUri), refreshToken);
AccessTokens tokens = GetTokens(this.RefreshUri, refreshTokenRequestBody);
this._accessToken = tokens.AccessToken;
this._refreshToken = tokens.RefreshToken;
this._expiration = tokens.Expiration;
}
catch (WebException)
{
this._error = "RefreshAccessToken failed likely due to an invalid client ID or refresh token";
}
return this._accessToken;
}
// The user either provided consent or canceled the request.
private void form_FormClosing(object sender, EventArgs e)
{
Application.Exit();
}
// Captures all consent traffic. Filter the traffic for the redirect
// URI. The URIs query string contains either the authorization code
// or an error.
private void browser_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
Dictionary<string, string> parameters = null;
if (!string.IsNullOrEmpty(e.Url.Query))
{
parameters = ParseFragment(e.Url.Query, new char[] { '&', '?' });
}
if (e.Url.AbsolutePath.Equals(RedirectPath))
{
if (parameters.ContainsKey("code"))
{
this._authorizationCode = parameters["code"];
}
else if (parameters.ContainsKey("error_description"))
{
this._error = WebUtility.UrlDecode(parameters["error_description"]);
}
this.Close();
}
else if (e.Url.AbsolutePath.Equals(ErrorPath))
{
if (parameters.ContainsKey("error_description"))
{
this._error = WebUtility.UrlDecode(parameters["error_description"]);
this.Close(); ;
}
}
}
// Parses the query string.
private Dictionary<string, string> ParseFragment(string queryString, char[] delimeters)
{
var parameters = new Dictionary<string, string>();
string[] pairs = queryString.Split(delimeters, StringSplitOptions.RemoveEmptyEntries);
foreach (string pair in pairs)
{
string[] nameValaue = pair.Split(new char[] { '=' });
parameters.Add(nameValaue[0], nameValaue[1]);
}
return parameters;
}
// Called by GetAccessToken and RefreshAccessToken to get an access token.
private static AccessTokens GetTokens(string uri, string body)
{
AccessTokens tokens = null;
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = body.Length;
using (Stream requestStream = request.GetRequestStream())
{
StreamWriter writer = new StreamWriter(requestStream);
writer.Write(body);
writer.Close();
}
var response = (HttpWebResponse)request.GetResponse();
using (Stream responseStream = response.GetResponseStream())
{
var reader = new StreamReader(responseStream);
string json = reader.ReadToEnd();
reader.Close();
tokens = JsonConvert.DeserializeObject(json, typeof(AccessTokens)) as AccessTokens;
}
return tokens;
}
}
// The body of the response from GetTokens is a JSON object that
// contains the following properties (and a couple of others
// that we're not capturing).
[JsonObject(MemberSerialization.OptIn)]
class AccessTokens
{
[JsonProperty("expires_in")]
public int Expiration { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
}
}