iOS SDK for ThunderID. Provides authentication and user management for native iOS and macOS applications.
.package(url: "https://github.com/thunder-id/ios-sdks", branch: "main")Note
To pin to a release instead of tracking main, replace branch: "main" with from: "x.x.x", using the latest version:
Set endpoints.backend to hand sign-in to a backend the app owns (the Backend for Frontend pattern of RFC 10017). The backend runs the OAuth flow and keeps the tokens, and the app only holds an opaque session handle.
ThunderIDConfig(
baseUrl: "https://app.example.com", // the backend
endpoints: .init(backend: .init(
session: "/auth/session", // relative: resolved against baseUrl
proxy: "/auth/api" // API calls go here; the backend maps them to the API
))
)baseUrlis the backend's base URL, and it must use HTTPS.- Each endpoint is either a path that starts with
/, resolved againstbaseUrl, or an absolute HTTPS URL, used as is. Unset endpoints default to paths under/api/auth. - API calls go to
{baseUrl}{proxy}: a path such as/usersis sent tohttps://app.example.com/auth/api/users, and a URL already under the proxy is sent as it is. The SDK's own calls,/users/meand the management operations, go there too. The backend maps the proxy to the API from its own configuration, so the app never needs the API's address. getGrantedPermissions()reads the permissions from the backend's session (scope), never from a token.
The signed-in user's organization units, and signing in to one of them:
getAccessibleOrganizationUnits(scope:limit:offset:)lists the units the user can access (/users/me/ous);getCurrentOrganizationUnit()reads the session'souId,ouHandleandouNameclaims, for display only.SignInOptions(organizationUnitId:)bounds a redirect sign-in to a unit (/ou/{ouId}/oauth2/authorize, and its code is redeemed at/ou/{ouId}/oauth2/token). In app-native mode,EmbeddedFlowRequestConfig(organizationUnitId:)orSignIn(applicationId:organizationUnitId:)sendsouIdwhen the flow starts. On a backend, it is passed to the backend's sign-in endpoint asouId.switchOrganizationUnit(_:options:)signs in again bounded to another unit: with a device secret (thedevice_ssoscope) it exchanges it at the unit's token endpoint with no browser, dropping a secret the server refuses withinvalid_requestorinvalid_grant; when there is none or the exchange fails, it opens the unit's authorization URL throughoptions.authorize, or, in app-native mode, starts the bounded flow with the SSO session handles that earlier flow steps returned insessionData, and returns.signInRequiredwhen the flow needs the user. The device secret and the kept session handles survive a switch, and a sign-out clears them. A failed switch leaves the session as it was; an unknown unit and one the application cannot be used from both fail withORGANIZATION_UNIT_UNAVAILABLE.OrganizationUnitSwitcherandBaseOrganizationUnitSwitchershow the current unit in a field that opens a menu of the units, with their handles, a check on the current one and paging, and switch to the one picked (onSelectlets the app take over). The sign-in flow components render a Resolve OU step from the server's description: a typed unit before sign-in, a picker of the eligible units after.ProjectList,ProjectCreate,ProjectDetailsandProjectSwitchermanage projects (Control Plane only).ProjectSwitchersetsThunderIDState.currentProject, kept per vendor; it is client-side context, not a sign-in. A project is not an organization unit: only applications and agents take one.ApplicationListandAgentListlist the current project's applications and agents (an explicitprojectIdwins; the server combines a project with no attribute filter, soAgentListhides its search while a project applies), andApplicationCreateandAgentCreatecreate in the project when the app passes no organization unit. Every other component keeps its own organization unit default (the signed-in user's unit).OrganizationUnitList,OrganizationUnitCreateandOrganizationUnitDetailsmanage organization units, their children and what they hold.SharingPolicieslists and, wheneditable, edits the sharing policies of a user, application, role or flow, with an organization unit's overlay rules whenouIdis given.
- Pushed authorization requests (RFC 9126). Set
pushedAuthorizationRequests: true, or let the server's metadata require it, and build the sign-in URL withsignInURL(options:): it posts the request to/oauth2/parand authorizes with the returnedrequest_uri.buildSignInURL(options:)cannot push, so it refuses whilepushedAuthorizationRequestsis on. - CIBA.
initiateCIBA(options:)andpollCIBA(authReqId:interval:), for a public client registered withtoken_endpoint_auth_methodnone. A configuration that holds aclientSecretis refused withUNSUPPORTED_OPERATION: an app cannot keep a secret, so a confidential client's CIBA is unsupported here. The endpoint comes from the server's metadata (ENDPOINT_NOT_ADVERTISEDwhen it names none). - Device authorization (RFC 8628). The device side,
startDeviceAuthorization()andpollDeviceAuthorization(deviceCode:interval:), is in theThunderIDcore, which also builds for tvOS 16;ThunderIDSwiftUIis iOS and macOS only, and browser sign-in is not available on tvOS. The device code is never shown or logged. The user side,verifyDeviceCode(userCode:applicationId:), runs the server'sDEVICE_GRANTflow in app-native mode; theDeviceGrantcomponent drives it, from the code (typed, or handed over from a scanned verification URI) to the approved, denied or expired outcome.
Playground/ is a SwiftUI app for trying every SDK capability from source: capability checks, a component explorer with generated SwiftUI snippets, a session inspector and an SDK event log. See Playground/README.md to run it against a local ThunderID server.
Please refer to the Contributing Guide for the different ways to contribute to this project and the relevant guidelines.
For code contributions, refer to the Contributing Code section for details on the prerequisites and instructions for running ThunderID in development mode.
This project is licensed under the Apache License 2.0.
