Mobile Session Validation

Frontend validation of sessions often serves as the first line of defense and is commonly implemented in many web applications. This approach checks the authenticity of user sessions directly from the user's mobile application. The main advantages of this approach are its simplicity and the reduction of server load, as validation happens on the mobile application side.

If you're looking to set up backend validation, check out our Backend Validation page. The session management article gives an overview of the session validation in Descope. This article focuses on the mobile application validation of sessions.

Install SDK

SwiftKotlinFlutterReact Native
// 1. Within XCode, go to File > Add Packages
// 2. Search for the URL of the git repo: https://github.com/descope/swift-sdk
// 3. Configure your desired dependency rule
// 4. Click Add Package
// 1. Within Android Studio, go to File > Project Structure > Dependencies > Add Dependency > 1 Library Dependency
// 2. Search for the dependency: "com.descope"
// 3. Configure your desired dependency rules
// 4. Click "Ok"
// 1. From your Flutter project directory root, install the Descope SDK by running: flutter pub add descope
// 2. Or, add Descope to your pubspec.yml by including this line: descope: ^0.6.0
// View the package on pub.dev: https://pub.dev/packages/descope
// 1. From your React Native project directory root, install the Descope SDK by running: npm i @descope/react-native-sdk
// View the package: https://github.com/descope/descope-react-native

Import and initialize SDK

SwiftKotlinFlutterReact Native
import DescopeKit
import AuthenticationServices

do {
    Descope.setup(projectId: "__ProjectID__")
    print("Successfully initialized Descope")
} catch {
    print("Failed to initialize Descope")
    print(error)
}
import android.app.Application
import com.descope.Descope

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        try {
            Descope.setup(this, projectId = "__ProjectID__")
        } catch (e: Exception) {
            Log.e("ERROR", e.stackTraceToString())
        }
    }
}
import 'package:descope/descope.dart';

// Where your application state is being created
Descope.setup('__ProjectID__');
await Descope.sessionManager.loadSession();
import { AuthProvider } from '@descope/react-native-sdk'

const AppRoot = () => {
  return (
    <AuthProvider
      projectId="__ProjectID__"
      // If the Descope project manages the token response in cookies, a custom domain
      // must be configured (e.g., https://auth.app.example.com)
      // and should be set as the baseUrl property.
      // baseUrl = "https://auth.app.example.com"
    >
      <App />
    </AuthProvider>
  )
}

Sending session token to application server

If you are using Mobile SDK or using Descope Flows, then your application must send the session token to you application server. The getSessionToken() function gets the sessionToken from local storage via JS which you can then include in your request.
SwiftKotlinFlutter
let sessionToken = Descope.sessionManager.session

// example fetch call with authentication header
fetch('your_application_server_url', {
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer '+ sessionToken,
  }
})
val session = Descope.sessionManager.session

// example fetch call with authentication header
fetch('your_application_server_url', {
  headers: {
    Accept: 'application/json',
    Authorization: 'Bearer '+ sessionToken,
  }
})
var url = Uri.parse('https://example.com/api/resource');

// Create an HTTP request
var request = http.Request('GET', url);

// Use the extension method to set the Authorization header from the session manager
await Descope.sessionManager.refreshSessionIfNeeded();

final sessionJwt = Descope.sessionManager.session?.sessionJwt;

if (sessionJwt != null) {
  request.headers['X-Auth-Token'] = sessionJwt;
} else {
  // unauthorized
  return;
}
 // Send the request
var response = await http.Response.fromStream(await request.send());

// Handle the response
if (response.statusCode == 200) {
  print('Successful response: ${response.body}');
} else {
  print('Failed response: ${response.statusCode}');
}

At any time, your application should only send the session token to your application and the application server should validate the session token using Descope Backend SDK.

Logout using Mobile SDK

If you are integrating using the Descope Mobile SDK, then you must use the Mobile SDK to logout. If you are using Descope Flows with React SDK, refer to the Quick Start for details. If you are Descope Mobile SDK without flows, then refer to the sample code below for logout.

SwiftKotlinFlutter
guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
try await Descope.auth.logout(refreshJwt: refreshJwt)
Descope.sessionManager.clearSession()
Descope.sessionManager.session?.refreshJwt?.run {
  Descope.auth.logout(this)
  Descope.sessionManager.clearSession()
}
final refreshJwt = Descope.sessionManager.session?.refreshJwt;
if (refreshJwt != null) {
  Descope.auth.logout(refreshJwt);
  Descope.sessionManager.clearSession();
}

Checking token expiration

One important step in validating a session token is to ensure that the token has not expired. Descope's WebJS SDK includes a method isJwtExpired for this purpose.
SwiftKotlinFlutter
if let session = Descope.sessionManager.session, session.refreshToken.isExpired {
  print("Session token has expired.")
} else {
  print("Session token is valid.")
}
if (Descope.sessionManager.session?.refreshToken?.isExpired == true) {
  println("Session token has expired.")
} else {
  println("Session token is valid.")
}
if (Descope.sessionManager.session?.refreshToken?.isExpired == true) {
    // Show main UI
} else {
    // Show login UI
}

Get Roles from Session Token

Depending on your application's requirements, you might need to know the roles of a user from their session token. Descope's SDKs provides the getJwtRoles function for this purpose.
SwiftKotlinFlutter
let sessionToken = Descope.sessionManager.session

let roles = sessionToken.roles()

print("User Roles: \(roles)")
val roles = Descope.sessionManager.session?.roles()

println("User Roles: ")
println(roles)
final roles = Descope.sessionManager.session?.roles();

print("User Roles:");
print(roles);

Get Permissions from Session Token

Depending on your application's requirements, you might need to know the permissions of a user from their session token. Descope's SDKs provides the getJwtPermissions function for this purpose.
SwiftKotlinFlutter
let sessionToken = Descope.sessionManager.session

let permissions = sessionToke.permissions()

print("User Permissions: \(permissions)")
val permissions = Descope.sessionManager.session?.permissions()

println("User Permissions: ")
println(permissions)
final permissions = Descope.sessionManager.session?.permissions();

print("User Permissions:");
print(permissions);

Starting a managed session

After a user finishes a sign in flow successfully you should create a DescopeSession object from the AuthenticationResponse value returned by all the authentication APIs.
SwiftKotlinFlutter
let authResponse = try await Descope.otp.verify(with: .email, loginId: "andy@example.com", code: "123456")
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
val authResponse = Descope.otp.verify(DeliverMethod.Email, "andy@example.com", "123456")
val session = DescopeSession(authResponse)
Descope.sessionManager.manageSession(session)
// if the user entered the right code the authentication is successful
final authResponse = await Descope.otp.verify(method: DeliveryMethod.email, loginId: 'andy@example.com', code: "123456");

// we create a DescopeSession object that represents an authenticated user session
final session = DescopeSession.fromAuthenticationResponse(authResponse);

// the session manager automatically takes care of persisting the session
// and refreshing it as needed
Descope.sessionManager.manageSession(session);

Authenticate outgoing requests

The session can then be used to authenticate outgoing requests to your backend with a bearer token authorization header.

SwiftKotlin
var request = URLRequest(url: url)
request.setAuthorizationHTTPHeaderField(from: Descope.sessionManager)
let (data, response) = try await URLSession.shared.data(for: request)
val connection = url.openConnection() as HttpsURLConnection
connection.setAuthorization(Descope.sessionManager)

Using the session JWT directly

If your backend uses a different authorization mechanism you can of course use the session JWT directly instead of the extension function:

SwiftKotlinFlutter
try await Descope.sessionManager.refreshSessionIfNeeded()
guard let sessionJwt = Descope.sessionManager.session?.sessionJwt else { throw ServerError.unauthorized }
request.setValue(sessionJwt, forHTTPHeaderField: "X-Auth-Token")
Descope.sessionManager.refreshSessionIfNeeded()
Descope.sessionManager.session?.sessionJwt?.apply {
    connection.setRequestProperty("X-Auth-Token", this)
} ?: throw ServerError.unauthorized
// Create a URL for the request
var url = Uri.parse('https://example.com/api/resource');

// Create an HTTP request
var request = http.Request('GET', url);

// Use the extension method to set the Authorization header from the session manager
request.setAuthorization(sessionManager);

Descope User

The DescopeUser struct represents an existing user in Descope.After a user is signed in with any authentication method the DescopeSession object keeps a DescopeUser value in its user property so the user's details are always available.In the example below we finalize an OTP authentication for the user by verifying the code. The authentication response has a user property which can be used directly or later on when it's kept in the DescopeSession.For instance, you can use the DescopeUser object to access the userId, loginIds, name, picture, email, verifiedEmail, phone, verifiedPhone, createdTime, and customAttributes.
SwiftKotlinFlutter
let authResponse = try await Descope.otp.verify(with: .email, loginId: "andy@example.com", code: "123456")
print("Finished OTP login for user: \(authResponse.user)")
Descope.sessionManager.session = DescopeSession(from: authResponse)
print("Created session for user \(descopeSession.user.userId)")
val authResponse = Descope.otp.verify(method = DeliveryMethod.Email, loginId = "andy@example.com", code = "123456")
print("Finished OTP login for user: ${authResponse.user}")

Descope.sessionManager.session = DescopeSession(authResponse)
print("Created session for user ${descopeSession.user.userId}")
// if the user entered the right code the authentication is successful
final authResponse = await Descope.otp.verify(method: DeliveryMethod.email, loginId: 'andy@example.com', code: "123456");

// we create a DescopeSession object that represents an authenticated user session
final session = DescopeSession.fromAuthenticationResponse(authResponse);

// the session manager automatically takes care of persisting the session
// and refreshing it as needed
Descope.sessionManager.manageSession(session);

// the user is available on the session object
print("Finished OTP login for user: ${session.user.userId}");
The details for a signed in user can be updated manually by calling auth.me with the refreshJwt from the active DescopeSession. If the operation is successful the call returns a new DescopeUser value.
SwiftKotlinFlutter
guard let session = Descope.sessionManager.session else { return }
let descopeUser = try await Descope.auth.me(refreshJwt: session.refreshJwt)
session.update(with: descopeUser)
final session = Descope.sessionManager.session;
if (session != null) {
  final descopeUser = await Descope.auth.me(session.refreshJwt);
  session.updateUser(descopeUser);
}

The DescopeUser struct contains the following attributes:

SwiftKotlinFlutter
/// The unique identifier for the user in Descope.
///
/// This value never changes after the user is created, and it always matches
/// the `Subject` (`sub`) claim value in the user's JWT after signing in.
var userId: String

/// The identifiers the user can sign in with.
///
/// This is a list of one or more email addresses, phone numbers, usernames, or any
/// custom identifiers the user can authenticate with.
var loginIds: [String]

/// The time at which the user was created in Descope.
var createdAt: Date

/// The user's full name.
var name: String?

/// The user's profile picture.
var picture: URL?

/// The user's email address.
///
/// If this is non-nil and the ``isVerifiedEmail`` flag is `true` then this email address
/// can be used to do email based authentications such as magic link, OTP, etc.
var email: String?

/// Whether the email address has been verified to be a valid authentication method
/// for this user. If ``email`` is `nil` then this is always `false`.
var isVerifiedEmail: Bool

/// The user's phone number.
///
/// If this is non-nil and the ``isVerifiedPhone`` flag is `true` then this phone number
/// can be used to do phone based authentications such as OTP.
var phone: String?

/// Whether the phone number has been verified to be a valid authentication method
/// for this user. If ``phone`` is `nil` then this is always `false`.
var isVerifiedPhone: Bool
/*
 * @property userId the unique identifier for the user in Descope.
 * This value never changes after the user is created, and it always matches
 * the `Subject` (`sub`) claim value in the user's JWT after signing in.
 * @property loginIds the identifiers the user can sign in with.
 * This is a list of one or more email addresses, phone numbers, usernames, or any
 * custom identifiers the user can authenticate with.
 * @property createdAt the time at which the user was created in Descope.
 * @property name the user's full name.
 * @property picture the user's profile picture.
 * @property email the user's email address.
 * If this is non-nil and the `isVerifiedEmail` flag is `true` then this email address
 * can be used to do email based authentications such as magic link, OTP, etc.
 * @property isVerifiedEmail whether the email address has been verified to be a valid authentication method
 * for this user. If `email` is `nil` then this is always `false`.
 * @property phone the user's phone number.
 * If this is non-nil and the `isVerifiedPhone` flag is `true` then this phone number
 * can be used to do phone based authentications such as OTP.
 * @property isVerifiedPhone whether the phone number has been verified to be a valid authentication method
 * for this user. If `phone` is `nil` then this is always `false`.
 */
data class DescopeUser(
    val userId: String,
    val loginIds: List<String>,
    val createdAt: Long,
    val name: String?,
    val picture: Uri?,
    val email: String?,
    val isVerifiedEmail: Boolean,
    val phone: String?,
    val isVerifiedPhone: Boolean,
)
/// Access DescopeUser via: 
DescopeUser? user = Descope.sessionManager.session?.user;

The `DescopeUser` class represents a user within the Descope system, containing various attributes related to identity and authentication.

Attributes

- `userId`: (String) The unique identifier for the user in Descope. This value never changes after the user is created, and it always matches the `Subject` (`sub`) claim value in the user's JWT after signing in.
- `loginIds`: (List<String>) The identifiers the user can sign in with. This is a list of one or more email addresses, phone numbers, usernames, or any custom identifiers the user can authenticate with.
- `createdAt`: (int) The time at which the user was created in Descope.
- `name`: (String?) The user's full name. Optional.
- `picture`: (Uri?) The user's profile picture. Optional.
- `email`: (String?) The user's email address. If this is non-null and the `isVerifiedEmail` flag is `true`, then this email address can be used to do email-based authentications such as magic link, OTP, etc. Optional.
- `isVerifiedEmail`: (bool) Whether the email address has been verified to be a valid authentication method for this user. If `email` is `nil`, then this is always `false`.
- `phone`: (String?) The user's phone number. If this is non-null and the `isVerifiedPhone` flag is `true`, then this phone number can be used to do phone-based authentications such as OTP. Optional.
- `isVerifiedPhone`: (bool) Whether the phone number has been verified to be a valid authentication method for this user. If `phone` is `null`, then this is always `false`.

Methods

- `fromJson`: A static method to create a `DescopeUser` object from a JSON map.
- `toJson`: A static method to convert a `DescopeUser` object into a JSON map.

Operators

- `==`: Equality operator to compare two `DescopeUser` objects.
- `hashCode`: Returns the hash code for this `DescopeUser` object.

Constructor

- `DescopeUser`: Constructor for creating a `DescopeUser` object, accepting all the above attributes as parameters.