Use this guide if you want to run authentication through your own server instead of Turnkey’s Auth Proxy. Your backend will call Turnkey’s public API for OTP, OAuth, and signup. The Swift SDK will:
Generate and protect the on-device API key pair
Create or resume sessions
Persist and expose session/user/wallet state via TurnkeyContext
If you prefer a no-backend setup, use the Auth Proxy instead.
The following high-level helpers on TurnkeyContext are designed for the Auth Proxy and will throw missingAuthProxyConfiguration when the proxy is disabled:
import TurnkeySwiftTurnkeyContext.configure(TurnkeyConfig( organizationId: "<your_org_id>", rpId: "<your_app_domain>" // required for passkeys // Do not set authProxyConfigId when using your own backend))
With the proxy disabled, TurnkeyContext.client will be stamper-only after you create/store a session.
Turnkey also offers Rust and Go SDKs that can be used to implement the backend endpoints. You can also use any other backend language you prefer as long as it can make HTTP requests to the Turnkey API.
Login endpoints like otpLogin and oauthLogin will require a public key to be passed in the request.You can use createKeyPair from the TurnkeyContext to generate a keypair for this purpose.
The private key will be generated in the Secure Enclave if available, otherwise it will be generated then securely stored within the Keychain. The public key will be returned and can be passed to your backend.
After that, TurnkeyContext.shared.client is ready for signed API calls, and authState will be .authenticated.If you have autoRefreshSession enabled under the auth object in the TurnkeyConfig configuration, the SDK will automatically refresh the session token when it expires.
You can also use the authState variable from the TurnkeyContext to check if the user is authenticated.
import SwiftUIimport TurnkeySwiftstruct ContentView: View { @EnvironmentObject private var turnkey: TurnkeyContext var body: some View { Group { switch turnkey.authState { case .authenticated: Text("Welcome back!") case .loading: ProgressView() default: Text("Please log in") } } }}