The Rownd SDK for iOS provides authentication, account and user profile management, deep linking, and more for native iPhone, iPad, and even macOS applications.
Using the Rownd platform, you can easily bring the same authentication that's on your website to your mobile apps. Or if you only authenticate users on your mobile apps, you can streamline the authentication process using Rownd's passwordless sign-in links, enabling you to seamlessly authenticate users from an app link sent to their email or phone number.
Once a user is authenticated, you can retrieve and update their profile information on the fly using native APIs. Leverage Rownd's pre-built mobile app components to give users profile management tools.
In Xcode, select your project file, select the main target, then scroll down to the "frameworks" section to add a package dependency to your project. See the official documentation for specific steps.
Enter this as the package repository url:
https://github.com/supertokens/supertokens-rownd-ios.git
Select the Rownd package product and add it to your app target.
The SuperTokens-backed Rownd SDK requires both Rownd and SuperTokens configuration during application launch. In your AppDelegate, set the Rownd runtime config first, then call Rownd.configure():
import Rownd
import UIKit
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let apiDomain = "https://api.example.com"
let apiBasePath = "/auth"
Task {
await Rownd.configure(
launchOptions: launchOptions,
appKey: "YOUR_ROWND_APP_KEY",
supertokens: RowndSuperTokensConfig(
appName: "Your App",
apiDomain: apiDomain,
apiBasePath: apiBasePath
)
)
}
return true
}Configuration notes:
Rownd.config.baseUrlshould be the Rownd Hub base URL used by the SDK, usuallyhttps://rownd-hub.supertokens.com.Rownd.config.deepLinkSchemeshould be the custom URL scheme your app registers and the SDK accepts, for examplerowndsupertokensor your app-specific scheme.RowndSuperTokensConfig.apiDomainshould point at the backend that hosts your SuperTokens plugin routes.Rownd.configure()also assigns this value toRownd.config.apiUrl.RowndSuperTokensConfig.apiBasePathmust match your backend SuperTokens API base path, usually/auth.
There are two deep-link values to configure:
- Deep link scheme: the custom URL scheme your app registers and the SDK accepts, for example
rowndsupertokens. - Deep link: the verified Universal Link on your custom Hub subdomain, for example
https://your-hub-subdomain.rownd-hub.supertokens.com.
The Hub can derive the custom scheme fallback from your custom Hub subdomain, so make sure your app's custom URL scheme matches the scheme the Hub will generate.
Add an Associated Domains entitlement for the Rownd hub host used by your app:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:your-subdomain.rownd-hub.supertokens.com</string>
</array>Register the custom URL scheme in your app's Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>rowndsupertokens</string>
</array>
</dict>
</array>The universal-link domain must also serve a valid Apple App Site Association file that includes your app's Team ID and bundle ID. Without that server-side AASA entry, iOS will open the link in the browser instead of delivering it to your app.
Finally, forward custom URL scheme links and universal links to Rownd:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
Rownd.handleSmartLink(url: url)
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
return Rownd.handleSmartLink(url: url)
}If you configure values through Xcode scheme environment variables, remember those variables are only present when launching from Xcode. Universal links that launch an already installed app will use values from code defaults, build settings, or Info.plist.
After initialization, your app will typically call Rownd.requestSignIn() at some point, if the user is not already authenticated. This will display the Rownd interface for authenticating the user. Once they complete the sign-in process, an access token and the user's profile information will be available to your app.
Rownd leverages an observeable architecture to expose data to your app. This means that as the Rownd state changes, an app can dynamically update without complicated logic. For example, a view can display different information based on the user's authentication status.
Here's an example SwiftUI view that displays different messages depending on the user's authenticated status:
import SwiftUI
import Rownd
struct MyView: View {
@StateObject var authState = Rownd.getInstance().state().subscribe { $0.auth }
@StateObject var user = Rownd.getInstance().state().subscribe { $0.user.data }
var body: some View {
VStack {
HStack {
Button(action: {
if authState.current.isAuthenticated {
Rownd.signOut()
} else {
Rownd.requestSignIn()
}
},
label: {
Text(!authState.current.isAuthenticated ? "Sign in" : "Sign out")
})
Spacer()
if authState.current.isAuthenticated {
Button(action: {
}, label: {
Text(user.current?["first_name"]?.value as? String)
})
}
}
.padding(.horizontal)
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView()
}
}class Auth {
private var authState = Rownd.getInstance().state().subscribe { $0.auth }
private var cancellables = Set<AnyCancellable>()
init() {
self.authState.$current
.sink { [weak self] state in
// Called whenever the auth state changes
guard !state.isLoading else { return }
guard state.auth.isAuthenticated else {
return
}
// User is authenticated. Change app state accordingly.
// This is also a good time to get the latest access token.
let accessToken = await Rownd.getAccessToken()
}
.store(in: &cancellables)
}
}You can subscribe to any state object that Rownd supports. Here's a list of available states and their structures:
public struct AuthState {
public var accessToken: String? // Current, valid access token for the user (valid for one hour)
public var isAuthenticated: Bool // Whether the user is currently authenticated
public var isAccessTokenValid: Bool // Whether the current access token is valid
public var isVerifiedUser: Bool? // Whether the current user has verified at least one identifier (e.g., email)
public var hasPreviouslySignedIn: Bool // Whether the app has been previously signed in before
}public struct UserState {
public var id: String? // The user's ID as known to Rownd
public var data: Dictionary<String, AnyCodable> // Contains key/value pairs for the current user based on your Rownd's app config
}Whenever your app needs to make an authenticated request to your backend, you'll need to get an access token. You can do this by calling await Rownd.getAccessToken(throwIfMissing: true). If the user is not authenticated, this function will throw an AuthenticationError.noAccessTokenPresent error.
If there is an issue fetching the access token (e.g., during a token refresh), an AuthenticationError.serverError or AuthenticationError.networkConnectionFailure error will be thrown. Server and network failures are automatically retried before throwing an error.
If the user is signed in, but the refresh token is expired, invalidated, or has been used previously, the user will be signed out and the function will throw an AuthenticationError.invalidRefreshToken error.
See the API reference for more information.
While most customizations are handled via the Rownd dashboard, there are a few things that have to be customized directly in the SDK.
The RowndCustomizations class exists to facilitate these customizations. It provides the following properties that may be subclassed or overridden.
sheetBackgroundColor: UIColor(default:light: .white,dark: .systemGray6; requires subclassing) - Allows changing the background color underlaying the bottom sheet that appears when signing in, managing the user account, etc.sheetCornerBorderRadius: CGFloat(default:25.0) - Modifies the curvature radius of the bottom sheet corners.loadingAnimation: Lottie.Animation?(default: nil) - Use this animation instead of the system default loading spinner (i.e.,UIActivityIndicatorVieworProgressView). Any animation compatible with Lottie should work, but will be scaled to fit a 1:1 aspect ratio (usually with aCGRectframe width/height of100)loadingAnimationUiView: UiView?(default: nil) - Display this UIView instead of the system default loading spinner. When using this option, you are responsible for starting the animation and ensuring that the view displays the animation vertically and horizontally centered in the view. Rownd will add and remove the view to a parent view as needed. Here's an example implementation.
To apply customizations, we recommend subclassing the RowndCustomizations class. Here's an example:
class AppCustomizations : RowndCustomizations {
override var sheetBackgroundColor: UIColor {
return UIColor(red: 31/255, green: 37/255, blue: 80/255, alpha: 1.0)
}
}
// AppDelegate.swift
import Rownd
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
Rownd.config.customizations = AppCustomizations() // Apply the customizations
Task {
await Rownd.configure(
launchOptions: launchOptions,
appKey: "YOUR_ROWND_APP_KEY",
supertokens: RowndSuperTokensConfig(
appName: "Your App",
apiDomain: "https://api.example.com",
apiBasePath: "/auth"
)
)
}
return true
}
}It's possible to access the Rownd state from within an app extension, like a widget. You'll need to include the Rownd package in the extension's dependencies and set up an app group for data sharing between the app and the extension. Without the app group, extensions will not be able to sync with your app's authentication state.
Follow these steps to configure your app and extension to work with Rownd:
-
Add an app group entitlement to both your app and any extensions that will use Rownd. This app group must be named like this:
<prefix>.io.rownd.sdk. For example, if you work at a company with the acme.com domain, your app group might look like this:com.acme.app.io.rownd.sdk. Rownd will store its data in this app group. Your app should store data in a separate app group to prevent any collisions. -
In your app's
AppDelegatefile as well as your extension's entry point, set the app group prefix you defined above viaRownd.config.appGroupPrefix = "<prefix>"(e.g.,Rownd.config.appGroupPrefix = "com.acme.app") -
In your extension, call
Rownd.configure()prior to accessing authentication state. Here's an example:Task { Rownd.config.appGroupPrefix = "group.rowndexample" let rowndState = await Rownd.configure( appKey: "YOUR_ROWND_APP_KEY", supertokens: RowndSuperTokensConfig( appName: "Your App", apiDomain: "https://api.example.com", apiBasePath: "/auth" ) ) var authStatus: String = "You are not authenticated. ☹️" if rowndState.auth.isAuthenticated == true { authStatus = "You are authenticated! 😁" } }
class SomeClass { private var authState = Rownd.getInstance().state().subscribe { $0.auth } private var cancellables = Set()
init() {
self.authState
.$current
.sink { [weak self] state in
WidgetCenter.shared.reloadAllTimelines()
}
.store(in: &cancellables)
}
}
</Note>
### Events
The Rownd SDK emits lifecycle events that you can listen to within your app. These events are primarily useful for detecting more granular aspects of a user's session (e.g., starting to sign in, completing sign-in, updated profile, etc.).
To listen to events, first create a class that conforms to the `RowndEventHandlerDelegate` protocol. It looks something like this:
```swift
import Foundation
import Rownd
class RowndEventHandler: RowndEventHandlerDelegate {
func handleRowndEvent(_ event: RowndEvent) {
switch event.event {
case .signInCompleted:
let userType = event.data?["user_type"]
let appVariantUserType = event.data?["app_variant_user_type"]
break
default:
break
}
}
}
Next, register the event handler delegate with the Rownd SDK:
Rownd.addEventHandler(RowndEventHandler())Once the event handler is registered, it will receive events as they occur. The RowndEvent object contains the event type and any associated data. The event types are defined in the RowndEventType enum.
Here's a list of events that the Rownd SDK emits and the corresponding data that should be present in the event data dictionary. Remember to write your code defensively, as the data dictionary may be missing keys in some cases.
| Event | Type | Payload |
|---|---|---|
| User started signing in | `.signInStarted` |
|
In addition to the state observable APIs, Rownd provides imperative APIs that you can call to request sign in, get and retrieve user profile information, retrieve a current access token, or encrypt user data with the user's local key.
Opens the Rownd sign-in dialog for authentication.
Rownd.requestSignIn(RowndSignInOptions(postSignInRedirect: "https://my-domain.com")) -> Void
Rownd.requestSignIn(RowndSignInOptions(postSignInRedirect: "https://my-domain.com", intent: .signIn)) -> Void
Opens the Rownd sign-in dialog for authentication, as above. The SDK and Hub normally handle redirects using your configured Universal Link and custom scheme fallback. Pass postSignInRedirect only when you need to override the default redirect target.
Requests a sign-in, but with a specific authentication provider (e.g., Sign in with Apple). Rownd treats this information as a hint. If the specified authentication provider is enabled within your Rownd app configuration, it will be honored. If not, Rownd will fall back to the default flow.
Supported values:
.appleId- Prompt user to sign in with their Apple ID.google- Prompt user to sign in with their Google account.guest- Sign in the user anonymously as a guest.
Passkeys, Firebase connection actions, legacy Rownd smart-link auth, and public legacy token exchange are not supported by the SuperTokens-backed SDK.
Some of the requestSignIn() methods accept an optional RowndSignInOptions parameter. This class contains the following properties:
postSignInRedirect: String?(not recommended) - The SDK and Hub normally handle redirects using your configured Universal Link and custom scheme fallback. Use this only when you need to override the default redirect target. If you provide a custom value, it must still resolve back to your app through a Universal Link or custom URL scheme your app handles.intent: RowndSignInIntent?- This option applies only when you have opted to split the sign-up/sign-in flow via the Rownd dashboard. Valid values are.signInor.signUp. If you don't set this value, the user will be presented with the unified sign-in/sign-up flow.
Clears the user's access token, removes the user's profile data, and returns the user to a completely unauthenticated state.
Revokes all tokens for the specified user causing them to be signed out on all devices.
Supported values:
.all- All devices
Assuming a user is signed-in, returns a valid access token, refreshing the current one if needed.
By default, this function will return nil if an access token cannot be returned, either because the user is not signed in or because the refresh token is invalid.
If an access token cannot be returned due to a temporary condition (e.g., inaccessible network), this function will throw an AuthenticationError indicating the failure reason (e.g., server or network error).
You may also set throwIfMissing to true to force an error to be thrown if an access token cannot be returned. This will provide more granular reasons for the failure. The possible error cases for AuthenticationError are:
.noAccessTokenPresent- the user is not signed in.invalidRefreshToken(details: String)- the refresh token was invalid (e.g., the token was expired, revoked, or a previous exchange failed to complete successfully). The user will be signed out..networkConnectionFailure(details: String)- a network condition prevented the token from being refreshed, even after several retries and should be re-attempted later.serverError(details: String)- an error occurred on the server and you should try again later
Example:
do {
let accessToken = try await Rownd.getAccessToken(throwIfMissing: true)
} catch {
switch error {
case AuthenticationError.noAccessTokenPresent:
// The user is not signed in
case AuthenticationError.invalidRefreshToken(let details):
// The refresh token was invalid. Request that the user sign in again.
case AuthenticationError.networkConnectionFailure(let details),
AuthenticationError.serverError(let details):
// Alert the user that they should try again due to some recoverable error
print("Server error occurred: \(details)")
}
}Returns the entire user profile as a dictionary object
Returns the value of a specific field in the user's data dictionary. "id" is a special case that will return the user's ID, even though it's technically not in the dictionary itself.
Your application code is responsible for knowing which type the value should cast to. If the cast fails or the entry doesn't exist, a nil value will be returned.
Replaces the user's data with that contained in the dictionary. This may overwrite existing values, but must match the schema you defined within your Rownd application dashboard. Any fields that are flagged as encrypted will be encrypted on-device prior to storing in Rownd's platform.
Hint: use AnyCodable.init(value) to conform your values to the required type.
Sets a specific user profile field to the provided value, overwriting if a value already exists. If the field is flagged as encrypted, it will be encrypted on-device prior to storing in Rownd's platform.
Hint: use AnyCodable.init(value) to conform your values to the required type.
The registerWebView method enables communication between the Rownd JavaScript SDK and native Rownd iOS SDK.
You should call registerWebView() with any web view that loads content containing the Rownd JavaScript SDK. This binding is required for certain operations like signing in with Google, where Google prohibits OAuth2 sign-in from within a web view. Once registered, the web view JavaScript SDK can send messages to the Swift code to perform sign-in with Google natively.
public static func registerWebView(_ webView: WKWebView) -> () -> Void- Parameter:
webView- TheWKWebViewinstance you want to register with Rownd. - Returns: A closure that can be called to deregister the web view when it's no longer needed.
Here's an example of how you might use registerWebView in your app:
import UIKit
import WebKit
import Rownd
class WebViewController: UIViewController {
var webView: WKWebView!
var deregisterWebView: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: self.view.bounds)
self.view.addSubview(webView)
// Register the web view with Rownd
deregisterWebView = Rownd.registerWebView(webView)
// Load a URL that uses the Rownd JavaScript SDK
if let url = URL(string: "https://your-web-app.com") {
webView.load(URLRequest(url: url))
}
}
deinit {
// Deregister the web view when the view controller is deallocated
deregisterWebView?()
}
}The example creates a WKWebView and registers it with Rownd. The web view loads a URL containing the Rownd JavaScript SDK. When the view controller deallocates, it calls the deregisterWebView closure to clean up the registration.