{"id":27443324,"url":"https://github.com/descope/descope-swift","last_synced_at":"2025-04-15T01:51:39.987Z","repository":{"id":132766771,"uuid":"556382693","full_name":"descope/descope-swift","owner":"descope","description":"Swift library (iOS) used to integrate with Descope","archived":false,"fork":false,"pushed_at":"2025-04-08T12:19:06.000Z","size":335,"stargazers_count":21,"open_issues_count":3,"forks_count":8,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-08T12:26:23.348Z","etag":null,"topics":["apple","authentication","descope","ios","iphone","mobile","sdk","swift","swift-sdk"],"latest_commit_sha":null,"homepage":"https://docs.descope.com","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/descope.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-23T18:21:42.000Z","updated_at":"2025-04-08T11:43:07.000Z","dependencies_parsed_at":"2023-10-14T22:23:44.566Z","dependency_job_id":"5bca5b58-f8a3-4625-ae53-2094eefec043","html_url":"https://github.com/descope/descope-swift","commit_stats":null,"previous_names":["descope/descope-swift","descope/swift-sdk"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/descope%2Fdescope-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/descope","download_url":"https://codeload.github.com/descope/descope-swift/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248991538,"owners_count":21194894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","authentication","descope","ios","iphone","mobile","sdk","swift","swift-sdk"],"created_at":"2025-04-15T01:51:39.295Z","updated_at":"2025-04-15T01:51:39.957Z","avatar_url":"https://github.com/descope.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DescopeKit\n\nDescopeKit is the Descope SDK for Swift. It provides convenient access\nto the Descope user management and authentication APIs for applications\nwritten in Swift. You can read more on the [Descope Website](https://descope.com).\n\n## Setup\n\nAdd the `DescopeKit` package using the Swift package manager. Within Xcode,\ngo to `File` \u003e `Add Package Dependencies` and enter the URL of the repo in\nthe search box at the top of the dialog:\n\n```\nhttps://github.com/descope/descope-swift\n```\n\nSet the dependency rule as appropriate and press the `Copy Dependency` button\nto confirm. The project navigator panel will show `DescopeKit` under package\ndependencies and you can `import DescopeKit` in your code.\n\nNote that the Descope SDK supports projects with a deployment target of iOS 13\nand above and macOS 12 and above.\n\n## Quickstart \n\nA Descope `Project ID` is required to initialize the SDK. Find it\non the [project page](https://app.descope.com/settings/project) in\nthe Descope Console.\n\n```swift\nimport DescopeKit\n\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n    Descope.setup(projectId: \"\u003cYour-Project-Id\u003e\")\n    return true\n}\n```\n\nYou can authenticate a user in your application by starting one of the\nauthentication methods. For example, let's use OTP via email: \n\n```swift\n// sends an OTP code to the given email address\ntry await Descope.otp.signUp(with: .email, loginId: \"andy@example.com\", details: nil)\n```\n\nWe finish the authentication by verifying the OTP code the user entered: \n\n```swift\n// if the user entered the right code the authentication is successful  \nlet authResponse = try await Descope.otp.verify(with: .email, loginId: \"andy@example.com\", code: code)\n\n// we create a DescopeSession object that represents an authenticated user session\nlet session = DescopeSession(from: authResponse)\n\n// the session manager takes care of saving the session to the keychain and\n// refreshing it for us as needed\nDescope.sessionManager.manageSession(session)\n```\n\nThe session manager will automatically load the session from the keychain\nthe next time the application is launched. At that point we might check if\nthere's a logged in user to decide which screen to show:\n\n```swift\nfunc initialViewController() -\u003e UIViewController {\n    // check if we have a valid session from a previous launch and that it hasn't expired yet \n    if let session = Descope.sessionManager.session, !session.refreshToken.isExpired {\n        print(\"Authenticated user found: \\(session.user)\")\n        return MainViewController()\n    }\n    return LoginViewController()\n}\n```\n\nWe use the active session to authenticate an outgoing API request to the\napplication's backend:\n\n```swift\nvar request = URLRequest(url: url)\ntry await request.setAuthorizationHTTPHeaderField(from: Descope.sessionManager)\nlet (data, response) = try await URLSession.shared.data(for: request)\n```\n\n## Session Management\n\nThe `DescopeSessionManager` class is used to manage an authenticated\nuser session for an application.\n\nThe session manager takes care of loading and saving the session as well\nas ensuring that it's refreshed when needed. For the default instances of\nthe `DescopeSessionManager` class this means using the keychain for secure\nstorage of the session and refreshing it a short while before it expires.\n\nOnce the user completes a sign in flow successfully you should set the\n`DescopeSession` object as the active session of the session manager.\n\n```swift\nlet authResponse = try await Descope.otp.verify(with: .email, loginId: \"andy@example.com\", code: \"123456\")\nlet session = DescopeSession(from: authResponse)\nDescope.sessionManager.manageSession(session)\n```\n\nThe session manager can then be used at any time to ensure the session\nis valid and to authenticate outgoing requests to your backend with a\nbearer token authorization header.\n\n```swift\nvar request = URLRequest(url: url)\ntry await request.setAuthorizationHTTPHeaderField(from: Descope.sessionManager)\nlet (data, response) = try await URLSession.shared.data(for: request)\n```\n\nIf your backend uses a different authorization mechanism you can of course\nuse the session JWT directly instead of the extension function. You can either\nadd another extension function on `URLRequest` such as the one above, or you\ncan do the following.\n\n```swift\ntry await Descope.sessionManager.refreshSessionIfNeeded()\nguard let sessionJwt = Descope.sessionManager.session?.sessionJwt else { throw ServerError.unauthorized }\nrequest.setValue(sessionJwt, forHTTPHeaderField: \"X-Auth-Token\")\n```\n\nWhen the application is relaunched the `DescopeSessionManager` loads any\nexisting session automatically, so you can check straight away if there's\nan authenticated user.\n\n```swift\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n    Descope.setup(projectId: \"...\")\n    if let session = Descope.sessionManager.session, !session.refreshToken.isExpired {\n        print(\"User is logged in: \\(session)\")\n    }\n    return true\n}\n```\n\nWhen the user wants to sign out of the application you only need to call\nthe `clearSession()` method to make the session manager clear its session\nand also delete it from the keychain.\n\n```swift\nDescope.sessionManager.clearSession()\n```\n\nIf you also want to remove the user's refresh JWT from the Descope servers\nonce it becomes redundant you can call the `Descope.auth.revokeSessions()`\nfunction. See its documentation for more details.\n\n## Flows\n\nWe can authenticate users by building and running Flows. Flows are built in the Descope \n[flow editor](https://app.descope.com/flows). The editor allows you to easily define both\nthe behavior and the UI that take the user through their authentication journey. Read more\nabout it in the Descope [getting started](https://docs.descope.com/build/guides/gettingstarted/)\nguide.\n\n### Define and host your flow\n\nBefore we can run a flow, it must first be defined and hosted. Every project comes with a\nset of predefined flows out of the box. You can customize your flows to suit your needs\nand host them somewhere on the web. Follow the [getting started](https://docs.descope.com/build/guides/gettingstarted/)\nguide for more details.\n\n### Enable Universal Links for Magic Link authentication\n\nIf your flows use Magic Link authentication, the user will need to be routed back to the\napp when they tap on the link in the authentication email message. If you don't intend to\nuse Magic Link authentication you can skip this step. Otherwise, see Apple's [universal links](https://developer.apple.com/ios/universal-links/)\nguide to learn more.\n\nWhen your application delegate is notified about a universal link being triggered, you'll\nneed to provide it to the flow so it can continue with the authentication. See the documentation\nfor `Descope.handleURL` for more details.\n\n```swift\nfunc application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -\u003e Void) -\u003e Bool {\n    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL else { return false }\n    let handled = Descope.handleURL(url)\n    return handled\n}\n```\n\n### Start the flow\n\nYou can use either a `DescopeFlowViewController` or a `DescopeFlowView` to run a flow.\nThe former provides a ready made component to present a flow modally with a few lines\nof code, while the latter can be used to show the flow however you want in your view\nhierarchy. See the documentation for both classes for more details.\n\n```swift\nfunc showLoginScreen() {\n    let flow = DescopeFlow(url: \"https://example.com/myflow\")\n\n    let flowViewController = DescopeFlowViewController()\n    flowViewController.delegate = self\n    flowViewController.start(flow: flow)\n\n    navigationController?.pushViewController(flowViewController, animated: true)\n}\n\nfunc flowViewControllerDidFinish(_ controller: DescopeFlowViewController, response: AuthenticationResponse) {\n    let session = DescopeSession(from: response)\n    Descope.sessionManager.manageSession(session)\n    showMainScreen()\n}\n```\n\n### Customizing the flow\n\nYou can use hooks to customize how the flow page looks or behaves when running as\na native flow. For example, these hooks will override the flow page to have a\ntransparent background and set a margin on the body element.\n\n```swift\nlet flow = DescopeFlow(url: \"https://example.com/myflow\")\nflow.hooks = [\n    .setTransparentBody,\n    .addStyles(selector: \"body\", rules: [\"margin: 16px\"]),\n]\n```\n\nSee the documentation for `DescopeFlowHook` for more examples on using hooks and how\nto create your own.\n\n## Authentication Methods\n\nHere are some examples for how to authenticate users:\n\n### OTP Authentication\n\nSend a user a one-time password (OTP) using your preferred delivery\nmethod (_email / SMS_). An email address or phone number must be\nprovided accordingly.\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```swift\n// Every user must have a loginId. All other user details are optional:\ntry await Descope.otp.signUp(with: .email, loginId: \"andy@example.com\", details: SignUpDetails(\n    name: \"Andy Rhoads\"\n))\n```\n\nThe user will receive a code using the selected delivery method. Verify\nthat code using:\n\n```swift\nlet authResponse = try await Descope.otp.verify(with: .email, loginId: \"andy@example.com\", code: \"123456\")\nlet session = DescopeSession(from: authResponse)\nDescope.sessionManager.manageSession(session)\n```\n\n### Magic Link\n\nSend a user a Magic Link using your preferred delivery method (_email / SMS_).\nThe Magic Link will redirect the user to page where the its token needs\nto be verified. This redirection can be configured in code, or globally\nin the [Descope Console](https://app.descope.com/settings/authentication/magiclink)\n\nThe user can either `sign up`, `sign in` or `sign up or in`\n\n```swift\n// If configured globally, the redirect URI is optional. If provided however, it will be used\n// instead of any global configuration\ntry await Descope.magiclink.signUp(with: .email, loginId: \"andy@example.com\", details: nil)\n```\n\nTo verify a magic link, your redirect page must call the validation function\non the token (`t`) parameter (`https://your-redirect-address.com/verify?t=\u003ctoken\u003e`):\n\n```swift\nlet authResponse = try await Descope.magiclink.verify(token: \"\u003ctoken\u003e\")\n```\n\n### OAuth\n\nWhen 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/)\nfeature to show a native authentication view that allows the user to login using the Apple ID\nthey are already logged into on their device. Note that the OAuth provider you choose to use\nmust be configured with the application's Bundle Identifier as the Client ID in the\n[Descope console](https://app.descope.com/settings/authentication/social).\n\n```swift\ndo {\n    showLoading(true)\n    let authResponse = try await Descope.oauth.native(provider: .apple, options: [])\n    let session = DescopeSession(from: authResponse)\n    Descope.sessionManager.manageSession(session)\n    showHomeScreen() \n} catch DescopeError.oauthNativeCancelled {\n    showLoading(false)\n    print(\"Authentication cancelled\")\n} catch {\n    showError(error)\n}\n```\n\nUsers can authenticate using any other social login providers, using the OAuth protocol via\na browser based authentication flow. Configure your OAuth settings on the [Descope console](https://app.descope.com/settings/authentication/social).\nTo start an OAuth authentication call:\n\n```swift\n// Choose an oauth provider out of the supported providers\n// If configured globally, the redirect URL is optional. If provided however, it will be used\n// instead of any global configuration.\n// Redirect the user to the returned URL to start the OAuth redirect chain\nlet authURL = try await Descope.oauth.start(provider: .github, redirectURL: \"exampleauthschema://my-app.com/handle-oauth\", options: [])\n```\n\nTake the generated URL and authenticate the user using `ASWebAuthenticationSession`\n(read more [here](https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service)).\nThe user will authenticate with the authentication provider, and will be\nredirected back to the redirect URL, with an appended `code` HTTP URL parameter.\nExchange it to validate the user:\n\n```swift\n// Start the authentication session\nlet session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: \"exampleauthschema\") { callbackURL, error in\n    // Extract the returned code\n    guard let url = callbackURL else { return }\n    let components = URLComponents(url: url, resolvingAgainstBaseURL: false)\n    guard let code = components?.queryItems?.first(where: { $0.name == \"code\" })?.value else { return }\n    \n    Task {\n        // Exchange code for session\n        let authResponse = try await Descope.oauth.exchange(code: code)\n        let session = DescopeSession(from: authResponse)\n        Descope.sessionManager.manageSession(session)\n    }\n}\n```\n\n### SSO/SAML\n\nUsers can authenticate to a specific tenant using SAML or Single Sign On.\nConfigure your SSO/SAML settings on the [Descope console](https://app.descope.com/settings/authentication/sso).\nTo start a flow call:\n\n```swift\n// Choose which tenant to log into\n// If configured globally, the return URL is optional. If provided however, it will be used\n// instead of any global configuration.\n// Redirect the user to the returned URL to start the SSO/SAML redirect chain\nlet authURL = try await Descope.sso.start(emailOrTenantName: \"my-tenant-ID\", redirectURL: \"exampleauthschema://my-app.com/handle-saml\", options: [])\n```\n\nTake the generated URL and authenticate the user using `ASWebAuthenticationSession`\n(read more [here](https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service)).\nThe user will authenticate with the authentication provider, and will be redirected\nback to the redirect URL, with an appended `code` HTTP URL parameter. Exchange it\nto validate the user:\n\n```swift\n// Start the authentication session\nlet session = ASWebAuthenticationSession(url: authURL, callbackURLScheme: \"exampleauthschema\") { callbackURL, error in\n\n    // Extract the returned code\n    guard let url = callbackURL else {return}\n    let component = URLComponents(url: url, resolvingAgainstBaseURL: false)\n    guard let code = component?.queryItems?.first(where: {$0.name == \"code\"})?.value else { return }\n\n    // ... Trigger asynchronously\n\n    // Exchange code for session\n    let authResponse = try await Descope.sso.exchange(code: code)\n    let session = DescopeSession(from: authResponse)\n    Descope.sessionManager.manageSession(session)\n}\n```\n\n### Passkeys\n\nUsers can authenticate by creating or using a [passkey](https://fidoalliance.org/passkeys/).\nConfigure your Passkey/WebAuthn settings on the [Descope console](https://app.descope.com/settings/authentication/webauthn).\nMake sure it is enabled and that the top level domain is configured correctly.\n\nAfter that, go through Apple's [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/public-private_key_authentication/supporting_passkeys/)\nguide, in particular be sure to have an associated domain configured for your app\nwith the `webcredentials` service type, whose value matches the top level domain\nyou configured in the Descope console earlier.\n\n```swift\ndo {\n    showLoading(true)\n    let authResponse = try await Descope.passkey.signUpOrIn(loginId: \"andy@example.com\", options: [])\n    let session = DescopeSession(from: authResponse)\n    Descope.sessionManager.manageSession(session)\n    showHomeScreen() \n} catch DescopeError.oauthNativeCancelled {\n    showLoading(false)\n    print(\"Authentication cancelled\")\n} catch {\n    showError(error)\n}\n```\n\n### TOTP Authentication\n\nThe user can authenticate using an authenticator app, such as Google Authenticator.\nSign up like you would using any other authentication method. The sign up response\nwill then contain a QR code `image` that can be displayed to the user to scan using\ntheir mobile device camera app, or the user can enter the `key` manually or click\non the link provided by the `provisioningURL`.\n\nExisting users can add TOTP using the `update` function.\n\n```swift\n// Every user must have a loginID. All other user information is optional\nlet totpResponse = try await Descope.totp.signUp(loginId: \"andy@example.com\", details: nil)\n\n// Use one of the provided options to have the user add their credentials to the authenticator\n// totpResponse.provisioningURL\n// totpResponse.key\n```\n\nThere are 3 different ways to allow the user to save their credentials in their\nauthenticator app - either by clicking the provisioning URL, scanning the QR\nimage or inserting the key manually. After that, signing in is done using the\ncode the app produces.\n\n```swift\nlet authResponse = try await Descope.totp.verify(loginId: \"andy@example.com\", code: \"987654\")\n```\n\n### Password Authentication\n\nCreate a new user that can later sign in with a password:\n\n```swift\nlet authResponse = try await Descope.password.signUp(loginId: \"andy@example.com\", password: \"securePassword123!\", details: SignUpDetails(\n    name: \"Andy Rhoads\"\n))\n\n// in another screen\n\nlet authResponse = try await Descope.password.signIn(loginId: \"andy@example.com\", password: \"securePassword123!\")\n```\n\nYou can update the current password for a logged in user:\n\n```swift\ntry await Descope.password.update(loginId: \"andy@example.com\", newPassword: \"newSecurePassword456!\", refreshJwt: \"user-refresh-jwt\")\n```\n\nYou can also replace the password for a user by providing both the new password and\nthe current one:\n\n```swift\nlet authResponse = try await Descope.password.replace(loginId: \"andy@example.com\", oldPassword: \"SecurePassword123!\", newPassword: \"NewSecurePassword456!\")\n```\n\nYou can also trigger a password reset email to be sent to the user:\n\n```swift\ntry await Descope.password.sendReset(loginId: \"andy@example.com\", redirectURL: \"appscheme://my-app.com/handle-reset\")\n```\n\n## Support\n\n#### Contributing\n\nIf anything is missing or not working correctly please open an issue or pull request.\n\n#### Learn more\n\nTo learn more please see the [Descope documentation](https://docs.descope.com).\n\n#### Contact us\n\nIf you need help you can hop on our [Slack community](https://www.descope.com/community) or send an email to [Descope support](mailto:support@descope.com).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdescope%2Fdescope-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdescope%2Fdescope-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdescope%2Fdescope-swift/lists"}