Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/descope/swift-sdk

Swift library (iOS) used to integrate with Descope
https://github.com/descope/swift-sdk

apple authentication descope ios iphone mobile sdk swift swift-sdk

Last synced: about 2 months ago
JSON representation

Swift library (iOS) used to integrate with Descope

Awesome Lists containing this project

README

        

# DescopeKit

DescopeKit is the Descope SDK for Swift. It provides convenient access
to the Descope user management and authentication APIs for applications
written in Swift. You can read more on the [Descope Website](https://descope.com).

## Setup

Add the `DescopeKit` package using the [Swift package manager](https://www.swift.org/package-manager/).

The SDK supports iOS 13 and above, and macOS 12 and above.

## Quickstart

A Descope `Project ID` is required to initialize the SDK. Find it
on the [project page](https://app.descope.com/settings/project) in
the Descope Console.

```swift
import DescopeKit

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Descope.setup(projectId: "")
return true
}
```

You can authenticate a user in your application by starting one of the
authentication methods. For example, let's use OTP via email:

```swift
// sends an OTP code to the given email address
try await Descope.otp.signUp(with: .email, loginId: "[email protected]", details: nil)
```

We finish the authentication by verifying the OTP code the user entered:

```swift
// if the user entered the right code the authentication is successful
let authResponse = try await Descope.otp.verify(with: .email, loginId: "[email protected]", code: code)

// we create a DescopeSession object that represents an authenticated user session
let session = DescopeSession(from: authResponse)

// the session manager takes care of saving the session to the keychain and
// refreshing it for us as needed
Descope.sessionManager.manageSession(session)
```

The session manager will automatically load the session from the keychain
the next time the application is launched. At that point we might check if
there's a logged in user to decide which screen to show:

```swift
func initialViewController() -> UIViewController {
// check if we have a valid session from a previous launch and that it hasn't expired yet
if let session = Descope.sessionManager.session, !session.refreshToken.isExpired {
print("Authenticated user found: \(session.user)")
return MainViewController()
}
return LoginViewController()
}
```

We use the active session to authenticate an outgoing API request to the
application's backend:

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

## Session Management

The `DescopeSessionManager` class is used to manage an authenticated
user session for an application.

The session manager takes care of loading and saving the session as well
as ensuring that it's refreshed when needed. For the default instances of
the `DescopeSessionManager` class this means using the keychain for secure
storage of the session and refreshing it a short while before it expires.

Once the user completes a sign in flow successfully you should set the
`DescopeSession` object as the active session of the session manager.

```swift
let authResponse = try await Descope.otp.verify(with: .email, loginId: "[email protected]", code: "123456")
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
```

The session manager can then be used at any time to ensure the session
is valid and to authenticate outgoing requests to your backend with a
bearer token authorization header.

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

If your backend uses a different authorization mechanism you can of course
use the session JWT directly instead of the extension function. You can either
add another extension function on `URLRequest` such as the one above, or you
can do the following.

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

When the application is relaunched the `DescopeSessionManager` loads any
existing session automatically, so you can check straight away if there's
an authenticated user.

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Descope.setup(projectId: "...")
if let session = Descope.sessionManager.session {
print("User is logged in: \(session)")
}
return true
}
```

When the user wants to sign out of the application we revoke the
active session and clear it from the session manager:

```swift
guard let refreshJwt = Descope.sessionManager.session?.refreshJwt else { return }
try await Descope.auth.logout(refreshJwt: refreshJwt)
Descope.sessionManager.clearSession()
```

You can customize how the `DescopeSessionManager` behaves by using
your own `storage` and `lifecycle` objects. See the documentation
for more details.

## Authentication Methods

Here are some examples for how to authenticate users:

### OTP Authentication

Send a user a one-time password (OTP) using your preferred delivery
method (_email / SMS_). An email address or phone number must be
provided accordingly.

The user can either `sign up`, `sign in` or `sign up or in`

```swift
// Every user must have a loginId. All other user details are optional:
try await Descope.otp.signUp(with: .email, loginId: "[email protected]", details: SignUpDetails(
name: "Andy Rhoads"
))
```

The user will receive a code using the selected delivery method. Verify
that code using:

```swift
let authResponse = try await Descope.otp.verify(with: .email, loginId: "[email protected]", code: "123456")
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
```

### Magic Link

Send a user a Magic Link using your preferred delivery method (_email / SMS_).
The Magic Link will redirect the user to page where the its token needs
to be verified. This redirection can be configured in code, or globally
in the [Descope Console](https://app.descope.com/settings/authentication/magiclink)

The user can either `sign up`, `sign in` or `sign up or in`

```swift
// If configured globally, the redirect URI is optional. If provided however, it will be used
// instead of any global configuration
try await Descope.magiclink.signUp(with: .email, loginId: "[email protected]", details: nil)
```

To verify a magic link, your redirect page must call the validation function
on the token (`t`) parameter (`https://your-redirect-address.com/verify?t=`):

```swift
let authResponse = try await Descope.magiclink.verify(token: "")
```

### OAuth

When a user wants to use social login with Apple you can leverage the [Sign in with Apple](https://developer.apple.com/sign-in-with-apple/)
feature to show a native authentication view that allows the user to login using the Apple ID
they are already logged into on their device. Note that the OAuth provider you choose to use
must be configured with the application's Bundle Identifier as the Client ID in the
[Descope console](https://app.descope.com/settings/authentication/social).

```swift
do {
showLoading(true)
let authResponse = try await Descope.oauth.native(provider: .apple, options: [])
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
showHomeScreen()
} catch DescopeError.oauthNativeCancelled {
showLoading(false)
print("Authentication cancelled")
} catch {
showError(error)
}
```

Users can authenticate using any other social login providers, using the OAuth protocol via
a browser based authentication flow. Configure your OAuth settings on the [Descope console](https://app.descope.com/settings/authentication/social).
To start an OAuth authentication call:

```swift
// Choose an oauth provider out of the supported providers
// If configured globally, the redirect URL is optional. If provided however, it will be used
// instead of any global configuration.
// Redirect the user to the returned URL to start the OAuth redirect chain
let authURL = try await Descope.oauth.start(provider: .github, redirectURL: "exampleauthschema://my-app.com/handle-oauth", options: [])
```

Take the generated URL and authenticate the user using `ASWebAuthenticationSession`
(read more [here](https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service)).
The user will authenticate with the authentication provider, and will be
redirected back to the redirect URL, with an appended `code` HTTP URL parameter.
Exchange it to validate the user:

```swift
// Start the authentication session
let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: "exampleauthschema") { callbackURL, error in
// Extract the returned code
guard let url = callbackURL else { return }
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
guard let code = components?.queryItems?.first(where: { $0.name == "code" })?.value else { return }

Task {
// Exchange code for session
let authResponse = try await Descope.oauth.exchange(code: code)
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
}
}
```

### SSO/SAML

Users can authenticate to a specific tenant using SAML or Single Sign On.
Configure your SSO/SAML settings on the [Descope console](https://app.descope.com/settings/authentication/sso).
To start a flow call:

```swift
// Choose which tenant to log into
// If configured globally, the return URL is optional. If provided however, it will be used
// instead of any global configuration.
// Redirect the user to the returned URL to start the SSO/SAML redirect chain
let authURL = try await Descope.sso.start(emailOrTenantName: "my-tenant-ID", redirectURL: "exampleauthschema://my-app.com/handle-saml", options: [])
```

Take the generated URL and authenticate the user using `ASWebAuthenticationSession`
(read more [here](https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service)).
The user will authenticate with the authentication provider, and will be redirected
back to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it
to validate the user:

```swift
// Start the authentication session
let session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: "exampleauthschema") { callbackURL, error in

// Extract the returned code
guard let url = callbackURL else {return}
let component = URLComponents(url: url, resolvingAgainstBaseURL: false)
guard let code = component?.queryItems?.first(where: {$0.name == "code"})?.value else { return }

// ... Trigger asynchronously

// Exchange code for session
let authResponse = try await Descope.sso.exchange(code: code)
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
}
```

### Passkeys

Users can authenticate by creating or using a [passkey](https://fidoalliance.org/passkeys/).
Configure your Passkey/WebAuthn settings on the [Descope console](https://app.descope.com/settings/authentication/webauthn).
Make sure it is enabled and that the top level domain is configured correctly.

After that, go through Apple's [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys/)
guide, in particular be sure to have an associated domain configured for your app
with the `webcredentials` service type, whose value matches the top level domain
you configured in the Descope console earlier.

```swift
do {
showLoading(true)
let authResponse = try await Descope.passkey.native(provider: .apple, options: [])
let session = DescopeSession(from: authResponse)
Descope.sessionManager.manageSession(session)
showHomeScreen()
} catch DescopeError.oauthNativeCancelled {
showLoading(false)
print("Authentication cancelled")
} catch {
showError(error)
}
```

### TOTP Authentication

The user can authenticate using an authenticator app, such as Google Authenticator.
Sign up like you would using any other authentication method. The sign up response
will then contain a QR code `image` that can be displayed to the user to scan using
their mobile device camera app, or the user can enter the `key` manually or click
on the link provided by the `provisioningURL`.

Existing users can add TOTP using the `update` function.

```swift
// Every user must have a loginID. All other user information is optional
let totpResponse = try await Descope.totp.signUp(loginId: "[email protected]", details: nil)

// Use one of the provided options to have the user add their credentials to the authenticator
// totpResponse.provisioningURL
// totpResponse.key
```

There are 3 different ways to allow the user to save their credentials in their
authenticator app - either by clicking the provisioning URL, scanning the QR
image or inserting the key manually. After that, signing in is done using the
code the app produces.

```swift
let authResponse = try await Descope.totp.verify(loginId: "[email protected]", code: "987654")
```

### Password Authentication

Authenticate users using a password.

#### Sign Up with Password

To create a new user that can later sign in with a password:

```swift
let authResponse = try await Descope.password.signUp(loginId: "[email protected]", password: "securePassword123!", details: SignUpDetails(
name: "Andy Rhoads"
))
```

#### Sign In with Password

Authenticate an existing user using a password:

```swift
let authResponse = try await Descope.password.signIn(loginId: "[email protected]", password: "securePassword123!")
```

#### Update Password

If you need to update a user's password:

```swift
try await Descope.password.update(loginId: "[email protected]", newPassword: "newSecurePassword456!", refreshJwt: "user-refresh-jwt")
```

#### Replace Password

To replace a user's password by providing their current password:

```swift
let authResponse = try await Descope.password.replace(loginId: "[email protected]", oldPassword: "SecurePassword123!", newPassword: "NewSecurePassword456!")
```

#### Send Password Reset Email

Initiate a password reset by sending an email:

```swift
try await Descope.password.sendReset(loginId: "[email protected]", redirectURL: "exampleauthschema://my-app.com/handle-reset")
```