OAuth 2.0 and OpenID Connect in .NET MAUI

πŸš€ OAuth 2.0 and OpenID Connect in .NET MAUI

A Secure, Native Approach with WebAuthenticator

Modern mobile apps demand secure, seamless authenticationβ€”and rolling your own auth is not an option anymore. Standards like OAuth 2.0 and OpenID Connect (OIDC) provide a robust, interoperable way to authenticate users and access APIs securely. In this guide, we’ll break down how to implement OAuth 2.0 + OIDC in .NET MAUI using WebAuthenticator, leveraging native browser flows for maximum security and compliance.


πŸ” Why OAuth 2.0 + OpenID Connect?

Before jumping into code, let’s clarify roles:

Concept Purpose Output
OAuth 2.0 Authorization (access to APIs) Access Token
OpenID Connect Authentication (user identity) ID Token (JWT)

πŸ‘‰ In mobile apps, you almost always use both together.


🧠 Why NOT Embedded WebViews?

Using WebView for authentication is strongly discouraged: ❌ No shared cookies
❌ Vulnerable to phishing
❌ Violates platform security guidelines (Apple/Google) βœ… Instead, use system browser via WebAuthenticator


βš™οΈ What is WebAuthenticator in .NET MAUI?

WebAuthenticator is a cross-platform API that:

  • Opens the system browser (Safari / Chrome Custom Tabs)
  • Handles deep link callbacks
  • Returns authentication results securely

🧩 Architecture Overview

MAUI App  
   ↓  
WebAuthenticator  
   ↓  
System Browser (OAuth Provider)  
   ↓  
Redirect URI (App)  
   ↓  
Access Token + ID Token

πŸ” Step 1: Register Your App (OAuth Provider)

Example providers:

  • Azure AD / Entra ID
  • Auth0
  • IdentityServer
  • Google / Facebook You’ll need:
  • Client ID
  • Redirect URI (custom scheme) Example:
myapp://callback

πŸ“± Step 2: Configure Redirect URI in MAUI

Android (AndroidManifest.xml)

<intent-filter>  
    <action android:name="android.intent.action.VIEW" />  
    <category android:name="android.intent.category.DEFAULT" />  
    <category android:name="android.intent.category.BROWSABLE" />  
  
    <data android:scheme="myapp" android:host="callback" />  
</intent-filter>

iOS (Info.plist)

<key>CFBundleURLTypes</key>  
<array>  
  <dict>  
    <key>CFBundleURLSchemes</key>  
    <array>  
      <string>myapp</string>  
    </array>  
  </dict>  
</array>

πŸ”‘ Step 3: Build the Authorization Request

var authUrl = new Uri(  
    "https://your-auth-server/authorize" +  
    "?client_id=YOUR_CLIENT_ID" +  
    "&response_type=code" +  
    "&scope=openid profile email" +  
    "&redirect_uri=myapp://callback" +  
    "&state=12345" +  
    "&code_challenge=XYZ" +  
    "&code_challenge_method=S256"  
);

⚠️ Note:

  • Use PKCE (Proof Key for Code Exchange) β†’ mandatory for mobile apps.

🌐 Step 4: Authenticate with WebAuthenticator

var result = await WebAuthenticator.AuthenticateAsync(  
    authUrl,  
    new Uri("myapp://callback")  
);

πŸ“₯ Step 5: Handle the Response

var code = result?.Properties["code"];

You’ll receive:

  • code β†’ exchange for tokens
  • optionally id_token

πŸ”„ Step 6: Exchange Code for Tokens

var httpClient = new HttpClient();  
  
var response = await httpClient.PostAsync(  
    "https://your-auth-server/token",  
    new FormUrlEncodedContent(new Dictionary<string, string>  
    {  
        { "grant_type", "authorization_code" },  
        { "client_id", "YOUR_CLIENT_ID" },  
        { "code", code },  
        { "redirect_uri", "myapp://callback" },  
        { "code_verifier", "YOUR_CODE_VERIFIER" }  
    })  
);  
  
var json = await response.Content.ReadAsStringAsync();

πŸ” Step 7: Store Tokens Securely

Use:

await SecureStorage.SetAsync("access_token", accessToken);

Never store tokens in: ❌ Preferences
❌ Plain text files


🧾 OAuth Flow Breakdown

Step Description
1 User taps login
2 Browser opens (WebAuthenticator)
3 User authenticates
4 Redirect to app
5 App receives authorization code
6 Exchange for tokens
7 Store securely

βš”οΈ Security Best Practices

βœ… Always Use PKCE

Prevents authorization code interception.


βœ… Use HTTPS Only

Never allow non-secure endpoints.


βœ… Validate State Parameter

if (result.Properties["state"] != expectedState)  
{  
    throw new Exception("Invalid state");  
}

βœ… Token Expiration Handling

Implement refresh tokens if supported.


βœ… Avoid Hardcoding Secrets

Mobile apps are public clients.


βš–οΈ WebAuthenticator vs WebView

Feature WebAuthenticator WebView
Security βœ… High ❌ Low
SSO Support βœ… Yes ❌ No
Compliance βœ… Apple/Google ❌ Rejected
UX βœ… Native ⚠️ Inconsistent

πŸ§ͺ Advanced: Using OpenID Connect ID Token

You can decode the ID Token (JWT):

var handler = new JwtSecurityTokenHandler();  
var jwt = handler.ReadJwtToken(idToken);  
  
var email = jwt.Claims.First(c => c.Type == "email").Value;

🧠 Pro Tip: Use Libraries When Needed

If you don’t want to implement manually:

  • IdentityModel.OidcClient
  • MSAL (Microsoft)
  • Auth0 SDK But WebAuthenticator remains the foundation in MAUI.

🏁 Conclusion

Implementing OAuth 2.0 + OpenID Connect in .NET MAUI using WebAuthenticator gives you: βœ… Native, secure authentication
βœ… Standards-compliant architecture
βœ… Cross-platform consistency
βœ… Future-proof identity integration If you're building modern mobile apps, this approach is not optionalβ€”it’s baseline security hygiene πŸ”


πŸ“š References


An unhandled error has occurred. Reload πŸ—™