.NET MAUI & PHP Quickstart
This is a quickstart guide to help you integrate Descope with your .NET MAUI & PHP application. Follow the steps below to get started.
Add the Descope.Maui Package
Next, we need to add the Descope .NET MAUI SDK to your app. It supports apps that target net10.0-ios (iOS 15+) and net10.0-android (Android 5.0+).
dotnet add package Descope.MauiRegister Descope with your Project ID
Register the Descope client in MauiProgram.cs. A Descope Project ID is required to initialize the SDK. You can find this ID on the project page in the Descope Console.
The redirect URI is the address Descope sends the user back to after the flow finishes. Use a custom scheme that belongs to your app, such as myapp://auth.
Note
You can also set the optional BaseUrl option if you're utilizing a custom domain within your Descope project (ex: https://auth.example.com). It must be an https URL.
using Descope.Maui;
builder.Services.AddDescope(options =>
{
options.ProjectId = "__ProjectID__";
options.RedirectUri = "myapp://auth";
// Optional: run a specific flow instead of the application's Flow Hosting URL
options.FlowId = "sign-up-or-in";
});
// register your pages so the DescopeClient can be injected into them
builder.Services.AddTransient<MainPage>();This registers a singleton DescopeClient, which you can inject into your pages or view models.
Define and Host your Flow
The SDK runs your flow in the system browser, using the Descope hosted flow page of your project's OIDC application. Every project comes with predefined flows out of the box, which you can customize in the Descope Console.
By default, the SDK runs the flow configured as the application's Flow Hosting URL. To run a different flow, set FlowId when registering the client, or pass it to LoginAsync.
(Optional) Use a Specific Federated Application
By default, users sign in through your project's default OIDC application. To sign in through another OIDC federated application in your project, set ClientId to that application's client ID, which you can find in the application's settings in the Descope Console:
builder.Services.AddDescope(options =>
{
options.ProjectId = "__ProjectID__";
options.RedirectUri = "myapp://auth";
options.ClientId = "<OIDC_APPLICATION_CLIENT_ID>";
});That application's Flow Hosting URL, approved redirect URLs and token settings then apply. Keep ProjectId set too, since the SDK sends its requests to your project's endpoints. Federated applications beyond the default one are a Pro+ feature.
Use a public client
A mobile app is a public client: anything you ship in it, including a client secret, can be extracted from the app. Only set the application's client ID, and never add a client secret or access key to your app. The SDK proves the sign-in came from your app with PKCE instead, so it doesn't need a secret.
Allow the Redirect URI
Descope only redirects back to your app when it accepts the redirect URI:
- If your OIDC application has approved redirect URLs, add the full redirect URI there, e.g.
myapp://auth. - Otherwise, if your project has Approved Domains, add the full redirect URI to them. A custom scheme doesn't match a domain entry such as
example.com.
Wildcards don't work for the redirect URI, so add the exact URI your app uses. If you use more than one, for example a different one per platform, add each of them.
Signing out with LogoutAsync(endBrowserSession: true), as recommended on Android, also sends the redirect URI to Descope as the post-logout redirect. That's always checked against your project's Approved Domains, so if your project has any, add the redirect URI there too.
If Apply Trusted Domains on Flow Execution is enabled in your project settings, also add the domain your flow is hosted on to Approved Domains: api.descope.com for flows hosted by Descope, or your custom domain. Otherwise Descope rejects the flow and the sign-in page stays blank. For example:
example.com, api.descope.com, myapp://authConfigure Android
On Android, the browser sends the redirect to an activity in your app. Add a subclass of DescopeCallbackActivity with your redirect URI's scheme:
using Android.App;
using Android.Content;
using Android.Content.PM;
using Descope.Maui;
[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter([Intent.ActionView],
Categories = [Intent.CategoryDefault, Intent.CategoryBrowsable],
DataScheme = "myapp")]
public class DescopeCallback : DescopeCallbackActivity { }On Android 11 and later, the app also needs to be able to see the browser. Add the following to AndroidManifest.xml:
<queries>
<intent>
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>The SDK saves the session with MAUI SecureStorage. If your app uses Android Auto Backup, exclude SecureStorage from the backup, since a restored copy can't be decrypted on the new device. See Selective backup in the MAUI docs.
Configure iOS
On iOS, the flow runs in an ASWebAuthenticationSession, which receives the redirect itself, so for most flows you don't need to register a URL scheme. Flows with magic links are the exception. See the magic links note in the next step.
The SDK stores the session in the keychain, so on the iOS simulator your app needs a keychain access group. Otherwise saving the session fails after the user signs in:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)com.example.myapp</string>
</array>
</dict>
</plist>Run your Flow
Call LoginAsync to open the flow in the system browser. It returns the user's session once they finish the flow, and the SDK stores and refreshes it for you.
using Descope.Maui;
public partial class MainPage : ContentPage
{
private readonly DescopeClient _descope;
public MainPage(DescopeClient descope)
{
InitializeComponent();
_descope = descope;
}
async void OnLoginClicked(object sender, EventArgs e)
{
try
{
var session = await _descope.LoginAsync();
// launch the "logged in" UI of your app
}
catch (DescopeException ex) when (ex.Code == DescopeErrorCode.Cancelled)
{
// the user closed the browser
}
catch (DescopeException ex)
{
// handle any other errors here
}
}
}Note
Magic links open in the device's browser, outside the sign-in session, so they need extra setup on iOS. See the README for details, or use OTP codes or enchanted links instead.
Utilize the .NET MAUI SDK Session Management Functions
Descope provides the SessionManager to check if the user is authenticated or get user data such as email, userId, etc. You can use these to customize the user experience:
LoadSessionAsync- restores the session saved by a previous launch, or returnsnullif the user isn't signed inSession- the current session, with theUserobject that contains the user's attributes (email, name, etc.), and the user's tenants and roles throughTenantIdsandRoles()SessionChanged- raised after the user signs in, the session is refreshed, or the user signs out. It's also raised withnullwhen Descope rejects the refresh token, for example because it was revoked, so show your signed out UI then too
To log the user out, call LogoutAsync on the DescopeClient. It revokes the refresh token and clears the session from storage.
On Android, the browser keeps its own Descope session, so the next sign-in could sign the same user back in without asking. Pass endBrowserSession: true on Android to sign the browser out too. On iOS, the flow runs in a private browser session by default, so there's nothing to sign out of.
protected override async void OnAppearing()
{
base.OnAppearing();
// restore the session from a previous launch
var session = await _descope.SessionManager.LoadSessionAsync();
if (session != null)
{
var email = session.User.Email;
var roles = session.Roles();
// the user is still signed in
}
}
async void OnLogoutClicked(object sender, EventArgs e)
{
// on Android, also sign out of the browser's Descope session
await _descope.LogoutAsync(endBrowserSession: DeviceInfo.Platform == DevicePlatform.Android);
// launch the "logged out" UI of your app
}To send the session JWT to your backend, use DescopeAuthorizationHandler, which refreshes the session when needed and adds it as a bearer token. Pass the hosts that may receive it, such as your API's. The JWT is only sent to those hosts, and only over https:
var api = new HttpClient(new DescopeAuthorizationHandler(_descope.SessionManager, "api.example.com")
{
InnerHandler = new HttpClientHandler(),
});At this point, you're done with the frontend. If you'd like more detail into frontend Session Management, check out our SDK. If you would like to also handle Session Validation in your backend, keep on reading!
Install Backend SDK
Install the SDK with Composer using the following command:
composer require descope/descope-phpSet up Environment file
Create a .env file in the root directory of your project with your Descope Project ID, which can be found in the Console
If you plan to use Management functions, include a Descope Management Key here as well, which can be found here.
DESCOPE_PROJECT_ID=<Descope Project ID>
DESCOPE_MANAGEMENT_KEY=<Descope Management Key>Setup Backend SDK
You'll need to initialize a DescopeSDK object using your Project ID.
If you're using a custom domain with your Descope project, make sure to export the Base URL (e.g. export DESCOPE_BASE_URI="__BaseURL__") when initializing descope_client.
require 'vendor/autoload.php';
use Descope\SDK\DescopeSDK;
$descopeSDK = new DescopeSDK([
'projectId' => $_ENV['DESCOPE_PROJECT_ID'],
'managementKey' => $_ENV['DESCOPE_MANAGEMENT_KEY'], // Optional, only used for Management functions
'baseUrl' => $_ENV['DESCOPE_BASE_URL'], // Optional, only used for custom domain or different regions
'debug' => false, // Optional, logs failed API requests to your PHP error log (default: false)
]);Implement Session Validation
You will need to then fetch the session token from the Authorization header of each request, and use the SDK to validate the token.
The frontend SDK will store the session token in either a cookie or your browser's local storage. If using a cookie, the token will be sent to your app server automatically with every request.
Note
By default, the aud claim in your session token is your Descope Project ID. After verify() succeeds, check the claims (for example with getClaims) so you only accept tokens issued for your application.
You can change the audience in a JWT Template if you need a custom value.
The $descopeSDK->verify($sessionToken) function can be used to verify a user's session as shown below. This returns a TRUE or FALSE depending on if the JWT is valid or not.
if (isset($_POST["sessionToken"])) {
if ($descopeSDK->verify($_POST["sessionToken"])) {
$_SESSION["user"] = json_decode($_POST["userDetails"], true);
$_SESSION["sessionToken"] = $_POST["sessionToken"];
session_write_close();
// User session validated and token saved
exit();
} else {
error_log("Session token verification failed.");
$descopeSDK->logout();
// Redirect to login page
exit();
}
} else {
error_log("Session token is not set in POST request.");
// Redirect to login page
exit();
}Checkpoint
Your application is now integrated with Descope. Please test with sign-up or sign-in use case.
Customize
Now that you have the end-to-end application working, you can choose to configure and personalize many different areas of Descope, including your brand, style, custom user authentication journeys, etc. We recommend starting with customizing your user-facing screens, such as signup and login.