Hi @Giorgio Sfiligoi ,
Thank you for the clarification.
By the “complete sample,” I was referring to the .NET MAUI test implementation I used for this scenario, rather than another sample from the Dropbox GitHub repository.
The implementation follows the same Authorization Code with PKCE approach, but adapts the callback handling for the MAUI platforms I tested. Windows uses a local loopback callback, while Android returns to the application through a platform-specific callback.
Below are the project structure and the main implementation pieces from my test implementation.
1. Project structure
I separated the authentication, Dropbox API, and UI responsibilities so that the platform-specific callback handling is easier to follow.
MauiApp/
├── Services/
│ ├── Authentication/
│ │ ├── DropboxAuthService.cs
│ │ ├── DropboxOAuthAttempt.cs
│ │ ├── DropboxOAuthProtocol.cs
│ │ ├── LoopbackOAuthReceiver.cs
│ │ └── OAuthCallbackRouter.cs
│ │
│ └── DropboxService.cs
│
└── Platforms/
└── Android/
└── DropboxCallbackActivity.cs
The main responsibilities are:
- DropboxAuthService.cs: coordinates the authorization flow and session persistence.
- DropboxOAuthAttempt.cs: creates the PKCE authorization attempt and holds the state, verifier, and callback information.
- DropboxOAuthProtocol.cs: validates the callback and exchanges the authorization code and verifier for tokens.
- LoopbackOAuthReceiver.cs: receives the Windows callback through the loopback address.
- OAuthCallbackRouter.cs and DropboxCallbackActivity.cs: handle the Android callback.
- DropboxService.cs: handles Dropbox file operations after authorization.
2. Authentication flow
The main flow in DropboxAuthService is:
public async Task SignInAsync(
string appKey,
CancellationToken cancellationToken)
{
var attempt = new DropboxOAuthAttempt(
appKey,
browser.RedirectUri);
var callback = await browser.AuthenticateAsync(
attempt,
cancellationToken);
var tokens = await protocol.ExchangeAsync(
attempt,
callback,
cancellationToken);
await PersistAsync(tokens);
}
This results in the following sequence:
Create PKCE attempt
→ Open Dropbox authorization page
→ Receive platform callback
→ Validate callback and state
→ Exchange authorization code + verifier
→ Persist the resulting session
In my test implementation, the resulting token information is persisted using MAUI SecureStorage.
3. PKCE token exchange
Once the callback has been received and validated, the authorization code is exchanged using the original PKCE verifier:
public Task<DropboxOAuthTokens> ExchangeAsync(
DropboxOAuthAttempt attempt,
Uri callback,
CancellationToken cancellationToken)
{
return RequestAsync(
attempt.AppKey,
new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["client_id"] = attempt.AppKey,
["code"] = attempt.ReadAuthorizationCode(callback),
["code_verifier"] = attempt.Verifier,
["redirect_uri"] = attempt.RedirectUri.AbsoluteUri
},
null,
cancellationToken);
}
The application therefore does not need to embed an App Secret for this PKCE exchange.
4. Windows callback
For Windows, the test implementation uses a temporary loopback listener:
using var listener =
new TcpListener(
IPAddress.Loopback,
attempt.RedirectUri.Port)
{
ExclusiveAddressUse = true
};
listener.Start(4);
using var connection =
await listener.AcceptTcpClientAsync(cancellationToken);
var stream = connection.GetStream();
// Read the HTTP request and reconstruct the callback URI.
if (Uri.TryCreate(
attempt.RedirectUri,
firstLine[1],
out var candidate)
&& attempt.MatchesCallback(candidate))
{
return candidate;
}
In my test implementation, this provides the callback directly to the PKCE flow without requiring index.html, JavaScript fragment forwarding, or the additional /token callback used by the original desktop implementation.
5. Android callback
For Android, the test implementation uses a platform-specific callback rather than running the desktop-style local HTTP callback flow inside the emulator.
The flow is:
Dropbox authorization
→ External browser
→ Android callback URI
→ DropboxCallbackActivity
→ OAuthCallbackRouter
→ Pending authentication attempt
→ PKCE token exchange
DropboxCallbackActivity receives the callback URI and passes it to OAuthCallbackRouter, which completes the pending authentication attempt.
This callback handling is the main platform-specific change I made when adapting the approach for MAUI.
6. Dropbox operations after authorization
Once the access token has been obtained, the Dropbox API logic remains separate from the authentication flow:
public async Task<DropboxPage> ListFilesAsync(
string accessToken,
string? cursor = null,
string folderPath = "")
{
var result = await ReadFolderAsync(
accessToken,
folderPath,
cursor,
200);
return new DropboxPage(
items,
result.HasMore ? result.Cursor : null);
}
The resulting access token can then be used with the existing Dropbox.Api operations for listing, reading, or downloading files.
Verification scope
This reflects the implementation path I used for testing. I verified the authorization flow on Windows and Android emulator
Physical Android devices, actual token expiration/refresh behavior, session restoration, and all cancellation scenarios were not part of the same end-to-end verification.
The key difference from the Dropbox desktop sample is therefore not the PKCE concept itself, but how the OAuth callback is received and returned to the MAUI application on Windows and Android.
I hope this helps clarify how I adapted the callback portion of the Dropbox sample for the MAUI test implementation.
If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.