Mobile Session Management

Descope extends support to mobile application development by offering SDKs tailored for Swift, Kotlin, and more. These SDKs incorporate robust token storage methodologies, utilizing the secure environment of the each device locally.

This guide shows how to effectively implement session validation and manage the tokens on your mobile app, with our Mobile SDKs.

The session management article gives an overview of the session validation in Descope.

If you're looking to set up backend validation, check out our Backend Validation page.

How Token Storage Works with Mobile SDKs

With Descope's Mobile SDKs at your disposal, you can bypass the necessity of manually saving tokens on the device. The SDKs, designed to cater to the unique needs of each platform, handle token storage and retrieval in an unobtrusive and secure manner in the background.

Swift

For Swift-based applications, Descope SDK uses iOS's Keychain Services to store, retrieve and manage the session and refresh tokens. Keychain Services provide a secure, encrypted storage mechanism for sensitive user information, including Descope tokens.

Kotlin

Descope's Kotlin SDK employs Android's native EncryptedSharedPreferences to securely store session and refresh tokens locally. The EncryptedSharedPreferences provide a robust, encrypted storage environment to protect sensitive user information, such as Descope tokens.

Flutter

Similarly, for Flutter applications, session and refresh tokens are stored using the native keychain on iOS and EncryptedSharedPreferences on Android, providing a secure storage solution.

Using our SDKs

Install SDK

// 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

Import and initialize SDK

import DescopeKit
import AuthenticationServices
 
do {
    Descope.setup(projectId: "__ProjectID__")
    print("Successfully initialized Descope")
} catch {
    print("Failed to initialize Descope")
    print(error)
}

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.

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

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.

guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
try await Descope.auth.logout(refreshJwt: 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.

if let session = Descope.sessionManager.session, session.refreshToken.isExpired {
  print("Session token has expired.")
} else {
  print("Session token is valid.")
}

Handling Authorization

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.

let sessionToken = Descope.sessionManager.session
 
let roles = sessionToken.roles()
 
print("User Roles: \(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.

let sessionToken = Descope.sessionManager.session
 
let permissions = sessionToke.permissions()
 
print("User Permissions: \(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.

let authResponse = try await Descope.otp.verify(with: .email, loginId: "andy@example.com", code: "123456")
let session = DescopeSession(from: authResponse)
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.

var request = URLRequest(url: url)
request.setAuthorizationHTTPHeaderField(from: Descope.sessionManager)
let (data, response) = try await URLSession.shared.data(for: request)

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:

try await Descope.sessionManager.refreshSessionIfNeeded()
guard let sessionJwt = Descope.sessionManager.session?.sessionJwt else { throw ServerError.unauthorized }
request.setValue(sessionJwt, forHTTPHeaderField: "X-Auth-Token")

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.

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)")

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.

guard let session = Descope.sessionManager.session else { return }
let descopeUser = try await Descope.auth.me(refreshJwt: session.refreshJwt)
session.update(with: descopeUser)

The DescopeUser struct contains the following attributes:

/// 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
Was this helpful?

On this page