Windows App SDK 中的
適用於 Windows 應用程式的 OAuth 2.0 背景
Windows Runtime(WinRT)WebAuthenticationBroker,主要為 UWP 應用程式設計,但在桌面應用程式中使用時會帶來一些挑戰。 關鍵問題包括與傳統型應用程式架構不相容的 ApplicationView相依性。 因此,開發者必須採取跨操作介面及額外程式碼的變通方法,將 OAuth 2.0 功能實作至 WinUI 及其他桌面應用程式中。
Windows App SDK 中的 OAuth2Manager API
OAuth2Manager API for Windows App SDK 提供一個符合開發者期望的簡化解決方案。 它提供無縫的 OAuth 2.0 功能,並在所有支援 Windows App SDK 的 Windows 平台上實現完整功能一致。 新的 API 消除了對繁瑣因應措施的需求,並簡化了在桌面應用程式中整合 OAuth 2.0 功能的過程。
OAuth2Manager 與 WinRT 中的 WebAuthenticationBroker 不同。 它更嚴格地遵循 OAuth 2.0 最佳實務 - 例如,使用使用者的預設瀏覽器。 API 的最佳做法來自 IETF (網際網路工程工作小組) OAuth 2.0 授權架構 RFC 6749、PKCE RFC 7636 和原生應用程式 OAuth 2.0 RFC 8252。
OAuth 2.0 程式碼範例
完整的 WinUI 範例應用程式可在 GitHub 取得。 下列各節提供使用 OAuth2Manager API 之最常見 OAuth 2.0 流程的代碼段。
授權碼要求
以下範例示範如何在 Windows App SDK 中使用 OAuth2Manager執行授權碼請求:
// Get the WindowId for the application window
Microsoft::UI::WindowId parentWindowId = this->AppWindow().Id();
AuthRequestParams authRequestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(L"my_client_id",
Uri(L"my-app:/oauth-callback/"));
authRequestParams.Scope(L"user:email user:birthday");
AuthRequestResult authRequestResult = co_await OAuth2Manager::RequestAuthWithParamsAsync(parentWindowId,
Uri(L"https://my.server.com/oauth/authorize"), authRequestParams);
if (AuthResponse authResponse = authRequestResult.Response())
{
//To obtain the authorization code
//authResponse.Code();
//To obtain the access token
DoTokenExchange(authResponse);
}
else
{
AuthFailure authFailure = authRequestResult.Failure();
NotifyFailure(authFailure.Error(), authFailure.ErrorDescription());
}
將授權碼交換為 access token
以下範例展示了如何使用 OAuth2Manager 來交換授權碼以取得存取令牌。
對於使用 PKCE 的 公用用戶端 (例如原生傳統型應用程式),請勿包含用戶端密碼。 PKCE 驗證碼則用來提供安全性:
AuthResponse authResponse = authRequestResult.Response();
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse);
// For public clients using PKCE, do not include ClientAuthentication
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams);
if (TokenResponse tokenResponse = tokenRequestResult.Response())
{
String accessToken = tokenResponse.AccessToken();
String tokenType = tokenResponse.TokenType();
// RefreshToken string null/empty when not present
if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
{
// ExpiresIn is zero when not present
DateTime expires = winrt::clock::now();
if (String expiresIn = tokenResponse.ExpiresIn(); std::stoi(expiresIn) != 0)
{
expires += std::chrono::seconds(static_cast<int64_t>(std::stoi(expiresIn)));
}
else
{
// Assume a duration of one hour
expires += std::chrono::hours(1);
}
//Schedule a refresh of the access token
myAppState.ScheduleRefreshAt(expires, refreshToken);
}
// Use the access token for resources
DoRequestWithToken(accessToken, tokenType);
}
else
{
TokenFailure tokenFailure = tokenRequestResult.Failure();
NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
}
對於擁有客戶端秘密的 機密客戶端(如 web apps 或服務),請包含 ClientAuthentication 參數:
AuthResponse authResponse = authRequestResult.Response();
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse);
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
L"my_client_secret");
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
// Handle the response as shown in the previous example
刷新存取權杖
以下範例展示了如何使用 OAuth2Manager 的 RefreshTokenAsync 方法來刷新 access 令牌。
對於使用 PKCE 的 公用用戶端 ,請省略參數 ClientAuthentication :
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForRefreshToken(refreshToken);
// For public clients using PKCE, do not include ClientAuthentication
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams);
if (TokenResponse tokenResponse = tokenRequestResult.Response())
{
UpdateToken(tokenResponse.AccessToken(), tokenResponse.TokenType(), tokenResponse.ExpiresIn());
//Store new refresh token if present
if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
{
// ExpiresIn is zero when not present
DateTime expires = winrt::clock::now();
if (String expiresInStr = tokenResponse.ExpiresIn(); !expiresInStr.empty())
{
int expiresIn = std::stoi(expiresInStr);
if (expiresIn != 0)
{
expires += std::chrono::seconds(static_cast<int64_t>(expiresIn));
}
}
else
{
// Assume a duration of one hour
expires += std::chrono::hours(1);
}
//Schedule a refresh of the access token
myAppState.ScheduleRefreshAt(expires, refreshToken);
}
}
else
{
TokenFailure tokenFailure = tokenRequestResult.Failure();
NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
}
對於具有用戶端密碼的 機密用戶端 ,請包含參數 ClientAuthentication :
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForRefreshToken(refreshToken);
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
L"my_client_secret");
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
// Handle the response as shown in the previous example
完成授權要求
如要完成通訊協定啟用的授權要求,您的應用程式應該處理 AppInstance.Activated 事件。 當您的應用程式具有自訂重新導向邏輯時,需要此事件。 完整範例可於GitHub取得。
使用下列程式碼:
void App::OnActivated(const IActivatedEventArgs& args)
{
if (args.Kind() == ActivationKind::Protocol)
{
auto protocolArgs = args.as<ProtocolActivatedEventArgs>();
if (OAuth2Manager::CompleteAuthRequest(protocolArgs.Uri()))
{
TerminateCurrentProcess();
}
DisplayUnhandledMessageToUser();
}
}