Skip to content

Commit 42c1e8f

Browse files
committed
Add OAuth2Manager conceptual doc
1 parent 44ded41 commit 42c1e8f

File tree

3 files changed

+291
-1
lines changed

3 files changed

+291
-1
lines changed

hub/apps/develop/security/index.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ Windows provides a wide variety of APIs related to security and identity scenari
1616

1717
### Windows App SDK APIs
1818

19-
The [Windows App SDK](../../windows-app-sdk/index.md) currently does not provide APIs related to security and identity scenarios other than a few helper APIs in the [Microsoft.Windows.Security.AccessControl](/windows/windows-app-sdk/api/winrt/microsoft.windows.security.accesscontrol) namespace. These APIs are related to named object sharing between packaged apps and Win32 applications.
19+
The [Windows App SDK](../../windows-app-sdk/index.md) provides APIs related to OAuth functionality. There are also a few helper APIs in the [Microsoft.Windows.Security.AccessControl](/windows/windows-app-sdk/api/winrt/microsoft.windows.security.accesscontrol) namespace. These APIs are related to named object sharing between packaged apps and Win32 applications.
20+
21+
| Article | Description |
22+
|---------|-------------|
23+
| [Implement OAuth functionality in Windows apps](oauth2.md) | The new OAuth2Manager in Windows App SDK enables desktop applications such as WinUI to seamlessly perform OAuth functionality across diverse Windows platforms. This article describes how to implement OAuth functionality in Windows apps with the Windows App SDK. |
2024

2125
### WinRT APIs
2226

hub/apps/develop/security/oauth2.md

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
---
2+
title: Implement OAuth functionality in Windows apps
3+
description: Learn how to implement OAuth functionality in Windows apps using the Windows App SDK's OAuth2Manager.
4+
ms.date: 01/05/2025
5+
ms.topic: concept-article
6+
keywords: windows, winui, winrt, dotnet, security
7+
#customer intent: As a Windows app developer, I want to learn how to implement OAuth functionality in my app so that I can securely authenticate users and access protected resources.
8+
---
9+
10+
# Implement OAuth functionality in Windows apps
11+
12+
The new OAuth2Manager in Windows App SDK enables desktop applications such as WinUI to seamlessly perform OAuth functionality across diverse Windows platforms. **OAuth2Manager** API intentionally doesn't provide APIs for the implicit request and resource owner password credential because of the security concerns that entails. It's recommended to use the authorization code grant type using Proof Key for Code Exchange (PKCE).
13+
14+
## OAuth background
15+
16+
The current WinRT [WebAuthenticationBroker](/uwp/api/windows.security.authentication.web.webauthenticationbroker), primarily designed for UWP applications, presents several challenges when used in desktop environments. Key issues include the dependency on ApplicationView, which are not compatible with desktop app frameworks. As a result, developers are forced to resort to workarounds involving interop interfaces and additional code to implement OAuth 2.0 functionality into their WinUI 3 desktop applications.
17+
18+
## OAuth2Manager API in Windows App SDK
19+
20+
The OAuth2Manager API for Windows App SDK aims to provide a streamlined solution that meets the expectations of developers. It offers seamless OAuth 2.0 capabilities with full feature parity across all Windows platforms supported by Windows App SDK. The new API eliminates the need for cumbersome workarounds and simplifies the process of incorporating OAuth 2.0 functionality into desktop applications.
21+
22+
The OAuth2Manager is different than the existing WinRT [WebAuthenticationBroker](/uwp/api/windows.security.authentication.web.webauthenticationbroker). It follows OAuth best practices more closely - e.g. using the user's default browser. The best practices for the API are taken from the IETF (Internet Engineering Task Force) OAuth 2.0 Authorization Framework [RFC 6749](https://tools.ietf.org/html/rfc6749), Proof Key for Code Exchange (PKCE) [RFC 7636](https://tools.ietf.org/html/rfc7636), and OAuth 2.0 for Native Apps [RFC 8252](https://tools.ietf.org/html/rfc8252).
23+
24+
## Perform OAuth 2.0 examples
25+
26+
### Authorization code request
27+
28+
The following example demonstrates how to perform an authorization code request using the OAuth2Manager API in Windows App SDK:
29+
30+
```cpp
31+
// Get the WindowId for the application window
32+
Microsoft::UI::WindowId parentWindowId = this->AppWindow().Id();
33+
34+
AuthRequestParams authRequestParams = AuthRequestParams::CreateForAuthorizationCodeRequest(L"my_client_id",
35+
Uri(L"my-app:/oauth-callback/"));
36+
authRequestParams.Scope(L"user:email user:birthday");
37+
38+
AuthRequestResult authRequestResult = co_await OAuth2Manager::RequestAuthWithParamsAsync(parentWindowId,
39+
Uri(L"https://my.server.com/oauth/authorize"), authRequestParams);
40+
if (AuthResponse authResponse = authRequestResult.Response())
41+
{
42+
//To obtain the authorization code
43+
//authResponse.Code();
44+
45+
//To obtain the access token
46+
DoTokenExchange(authResponse);
47+
}
48+
else
49+
{
50+
AuthFailure authFailure = authRequestResult.Failure();
51+
NotifyFailure(authFailure.Error(), authFailure.ErrorDescription());
52+
}
53+
```
54+
55+
```csharp
56+
// Get the WindowId for the application window
57+
Microsoft.UI.WindowId parentWindowId = this.AppWindow.Id;
58+
59+
AuthRequestParams authRequestParams = AuthRequestParams.CreateForAuthorizationCodeRequest("my_client_id",
60+
new Uri("my-app:/oauth-callback/"));
61+
authRequestParams.Scope = "user:email user:birthday";
62+
63+
AuthRequestResult authRequestResult = await OAuth2Manager.RequestAuthWithParamsAsync(parentWindowId,
64+
new Uri("https://my.server.com/oauth/authorize"), authRequestParams);
65+
66+
if (AuthResponse authResponse == authRequestResult.Response)
67+
{
68+
//To obtain the authorization code
69+
//authResponse.Code;
70+
71+
//To obtain the access token
72+
DoTokenExchange(authResponse);
73+
}
74+
else
75+
{
76+
AuthFailure authFailure = authRequestResult.Failure;
77+
NotifyFailure(authFailure.Error, authFailure.ErrorDescription);
78+
}
79+
```
80+
81+
### Exchange authorization code for access token
82+
83+
The following example demonstrates how to exchange an authorization code for an access token using the OAuth2Manager API in Windows App SDK:
84+
85+
```cpp
86+
AuthResponse authResponse = authRequestResult.Response();
87+
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForAuthorizationCodeRequest(authResponse);
88+
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
89+
L"my_client_secret");
90+
91+
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
92+
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
93+
if (TokenResponse tokenResponse = tokenRequestResult.Response())
94+
{
95+
String accessToken = tokenResponse.AccessToken();
96+
String tokenType = tokenResponse.TokenType();
97+
98+
// RefreshToken string null/empty when not present
99+
if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
100+
{
101+
// ExpiresIn is zero when not present
102+
DateTime expires = winrt::clock::now();
103+
if (String expiresIn = tokenResponse.ExpiresIn(); expiresIn != 0)
104+
{
105+
expires += std::chrono::seconds(static_cast<int64_t>(expiresIn));
106+
}
107+
else
108+
{
109+
// Assume a duration of one hour
110+
expires += std::chrono::hours(1);
111+
}
112+
113+
//Schedule a refresh of the access token
114+
myAppState.ScheduleRefreshAt(expires, refreshToken);
115+
}
116+
117+
// Use the access token for resources
118+
DoRequestWithToken(accessToken, tokenType);
119+
}
120+
else
121+
{
122+
TokenFailure tokenFailure = tokenRequestResult.Failure();
123+
NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
124+
}
125+
```
126+
127+
```csharp
128+
AuthResponse authResponse = authRequestResult.Response;
129+
TokenRequestParams tokenRequestParams = TokenRequestParams.CreateForAuthorizationCodeRequest(authResponse);
130+
ClientAuthentication clientAuth = ClientAuthentication.CreateForBasicAuthorization("my_client_id",
131+
"my_client_secret");
132+
133+
TokenRequestResult tokenRequestResult = await OAuth2Manager.RequestTokenAsync(
134+
new Uri("https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
135+
136+
if (TokenResponse tokenResponse == tokenRequestResult.Response)
137+
{
138+
string accessToken = tokenResponse.AccessToken;
139+
string tokenType = tokenResponse.TokenType;
140+
141+
// RefreshToken string null/empty when not present
142+
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
143+
{
144+
// ExpiresIn is zero when not present
145+
DateTime expires = DateTime.Now;
146+
if (tokenResponse.ExpiresIn != 0)
147+
{
148+
expires += TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
149+
}
150+
else
151+
{
152+
// Assume a duration of one hour
153+
expires += TimeSpan.FromHours(1);
154+
}
155+
156+
//Schedule a refresh of the access token
157+
myAppState.ScheduleRefreshAt(expires, tokenResponse.RefreshToken);
158+
}
159+
160+
// Use the access token for resources
161+
DoRequestWithToken(accessToken, tokenType);
162+
}
163+
else
164+
{
165+
TokenFailure tokenFailure = tokenRequestResult.Failure;
166+
NotifyFailure(tokenFailure.Error, tokenFailure.ErrorDescription);
167+
}
168+
```
169+
170+
### Refresh an access token
171+
172+
The following example shows how to refresh an access token using the OAuth2Manager API in Windows App SDK:
173+
174+
```cpp
175+
TokenRequestParams tokenRequestParams = TokenRequestParams::CreateForRefreshToken(refreshToken);
176+
ClientAuthentication clientAuth = ClientAuthentication::CreateForBasicAuthorization(L"my_client_id",
177+
L"my_client_secret");
178+
TokenRequestResult tokenRequestResult = co_await OAuth2Manager::RequestTokenAsync(
179+
Uri(L"https://my.server.com/oauth/token"), tokenRequestParams, clientAuth));
180+
if (TokenResponse tokenResponse = tokenRequestResult.Response())
181+
{
182+
UpdateToken(tokenResponse.AccessToken(), tokenResponse.TokenType(), tokenResponse.ExpiresIn());
183+
184+
//Store new refresh token if present
185+
if (String refreshToken = tokenResponse.RefreshToken(); !refreshToken.empty())
186+
{
187+
// ExpiresIn is zero when not present
188+
DateTime expires = winrt::clock::now();
189+
if (String expiresIn = tokenResponse.ExpiresIn(); expiresIn != 0)
190+
{
191+
expires += std::chrono::seconds(static_cast<int64_t>(expiresIn));
192+
}
193+
else
194+
{
195+
// Assume a duration of one hour
196+
expires += std::chrono::hours(1);
197+
}
198+
199+
//Schedule a refresh of the access token
200+
myAppState.ScheduleRefreshAt(expires, refreshToken);
201+
}
202+
}
203+
else
204+
{
205+
TokenFailure tokenFailure = tokenRequestResult.Failure();
206+
NotifyFailure(tokenFailure.Error(), tokenFailure.ErrorDescription());
207+
}
208+
```
209+
210+
```csharp
211+
TokenRequestParams tokenRequestParams = TokenRequestParams.CreateForRefreshToken(refreshToken);
212+
ClientAuthentication clientAuth = ClientAuthentication.CreateForBasicAuthorization("my_client_id",
213+
"my_client_secret");
214+
TokenRequestResult tokenRequestResult = await OAuth2Manager.RequestTokenAsync(
215+
new Uri("https://my.server.com/oauth/token"), tokenRequestParams, clientAuth);
216+
if (TokenResponse tokenResponse == tokenRequestResult.Response)
217+
{
218+
UpdateToken(tokenResponse.AccessToken, tokenResponse.TokenType, tokenResponse.ExpiresIn);
219+
220+
//Store new refresh token if present
221+
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
222+
{
223+
// ExpiresIn is zero when not present
224+
DateTime expires = DateTime.Now;
225+
if (tokenResponse.ExpiresIn != 0)
226+
{
227+
expires += TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
228+
}
229+
else
230+
{
231+
// Assume a duration of one hour
232+
expires += TimeSpan.FromHours(1);
233+
}
234+
235+
//Schedule a refresh of the access token
236+
myAppState.ScheduleRefreshAt(expires, tokenResponse.RefreshToken);
237+
}
238+
}
239+
else
240+
{
241+
TokenFailure tokenFailure = tokenRequestResult.Failure;
242+
NotifyFailure(tokenFailure.Error, tokenFailure.ErrorDescription);
243+
}
244+
```
245+
246+
### Complete an authorization request
247+
248+
Finally, to complete an authorization request from a protocol activation, use the following code:
249+
250+
```cpp
251+
void App::OnActivated(const IActivatedEventArgs& args)
252+
{
253+
if (args.Kind() == ActivationKind::Protocol)
254+
{
255+
auto protocolArgs = args.as<ProtocolActivatedEventArgs>();
256+
if (OAuth2Manager::CompleteAuthRequest(protocolArgs.Uri()))
257+
{
258+
TerminateCurrentProcess();
259+
}
260+
261+
DisplayUnhandledMessageToUser();
262+
}
263+
}
264+
```
265+
266+
```csharp
267+
protected override void OnActivated(IActivatedEventArgs args)
268+
{
269+
if (args.Kind == ActivationKind.Protocol)
270+
{
271+
ProtocolActivatedEventArgs protocolArgs = args as ProtocolActivatedEventArgs;
272+
if (OAuth2Manager.CompleteAuthRequest(protocolArgs.Uri))
273+
{
274+
TerminateCurrentProcess();
275+
}
276+
277+
DisplayUnhandledMessageToUser();
278+
}
279+
}
280+
```
281+
282+
## Related content
283+
284+
[WebAuthenticationBroker](/uwp/api/windows.security.authentication.web.webauthenticationbroker)

hub/apps/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ items:
231231
href: develop/security/credential-locker.md
232232
- name: Fingerprint biometrics
233233
href: develop/security/fingerprint-biometrics.md
234+
- name: Implement OAuth functionality
235+
href: develop/security/oauth2.md
234236
- name: Share certificates between apps
235237
href: develop/security/share-certificates.md
236238
- name: Smart cards

0 commit comments

Comments
 (0)