| name | mojoauth-oidc-hosted-page-ios |
| description | Implement passwordless authentication in native iOS/Swift applications using MojoAuth OIDC with AppAuth. |
Implement MojoAuth OIDC (iOS / Swift)
This expert AI assistant guide walks you through integrating passwordless authentication into an existing iOS application using MojoAuth's Hosted Login Page as an OIDC identity provider via AppAuth for iOS. MojoAuth handles all authentication methods (Magic Links, Email OTP, SMS OTP, Social Login, Passkeys) through its hosted page.
1. Prerequisites
- An existing iOS 15+ application (Swift 5.7+) with a login screen.
- Xcode 14+ installed.
- An active MojoAuth account.
- MojoAuth OIDC Setup Guide
- Required library:
openid/AppAuth-iOS.
2. Implementation Steps
Step 1: Configure Application in MojoAuth
- Log in to the MojoAuth Dashboard.
- Note your MojoAuth Domain (e.g.,
your-app.mojoauth.com).
- Configure the callback URI using a custom scheme (e.g.,
com.example.myapp://auth/callback).
- Retrieve Client ID.
- Enable your preferred authentication methods.
Note: For native/mobile apps, use Authorization Code with PKCE (no Client Secret on the device).
Step 2: Modify the Existing iOS Project
Substep 2.1: Add Dependencies
Add AppAuth via Swift Package Manager:
- In Xcode, go to File > Add Package Dependencies.
- Enter:
https://github.com/openid/AppAuth-iOS.git.
- Select the latest stable version and add to your target.
Or via CocoaPods:
pod 'AppAuth'
pod install
Substep 2.2: Configure URL Scheme
Add a custom URL scheme in your Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.example.myapp</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
</dict>
</array>
Substep 2.3: Configure OIDC
Create an auth configuration helper (e.g., AuthConfig.swift):
import Foundation
struct AuthConfig {
static let issuerURL = URL(string: "https://your-app.mojoauth.com")!
static let clientID = "your_client_id"
static let redirectURI = URL(string: "com.example.myapp://auth/callback")!
static let scopes = ["openid", "profile", "email"]
}
Substep 2.4: Create Auth Manager
Create a centralised auth manager (e.g., AuthManager.swift):
import UIKit
import AppAuth
class AuthManager: NSObject {
static let shared = AuthManager()
var currentAuthorizationFlow: OIDExternalUserAgentSession?
var authState: OIDAuthState?
func login(from viewController: UIViewController, completion: @escaping (Result<OIDAuthState, Error>) -> Void) {
OIDAuthorizationService.discoverConfiguration(forIssuer: AuthConfig.issuerURL) { config, error in
guard let config = config else {
completion(.failure(error ?? NSError(domain: "OIDC", code: -1, userInfo: [NSLocalizedDescriptionKey: "Discovery failed"])))
return
}
let request = OIDAuthorizationRequest(
configuration: config,
clientId: AuthConfig.clientID,
scopes: AuthConfig.scopes,
redirectURL: AuthConfig.redirectURI,
responseType: ,
additionalParameters:
)
.currentAuthorizationFlow .authState(
byPresenting: request,
presenting: viewController
) { authState, error
authState authState {
.authState authState
()
completion(.success(authState))
} {
()
completion(.failure(error (domain: , code: )))
}
}
}
}
() {
authState
}
}
Substep 2.5: Handle Redirect in AppDelegate / SceneDelegate
import AppAuth
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
if let flow = AuthManager.shared.currentAuthorizationFlow,
flow.resumeExternalUserAgentFlow(with: url) {
AuthManager.shared.currentAuthorizationFlow = nil
return true
}
return false
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
if let flow = AuthManager.shared.currentAuthorizationFlow,
flow.resumeExternalUserAgentFlow(with: url) {
AuthManager.shared.currentAuthorizationFlow =
}
}
Substep 2.6: Update Login View Controller
Since MojoAuth handles all authentication on its Hosted Login Page, your login screen only needs a "Sign In" button:
import UIKit
class LoginViewController: UIViewController {
private let signInButton = UIButton(type: .system)
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .systemBackground
title = "Welcome"
let titleLabel = UILabel()
titleLabel.text = "Welcome"
titleLabel.font = .systemFont(ofSize: 28, weight: .bold)
titleLabel.textAlignment = .center
let subtitleLabel = UILabel()
subtitleLabel.text = "Sign in with your preferred method"
subtitleLabel.font = .systemFont(ofSize: 14)
subtitleLabel.textColor = .secondaryLabel
subtitleLabel.textAlignment = .center
signInButton.setTitle("Sign In with MojoAuth", for: .normal)
signInButton.addTarget(self, action: #selector(handleSignIn), for: .touchUpInside)
signInButton.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold)
let poweredByLabel = ()
poweredByLabel.text
poweredByLabel.font .systemFont(ofSize: )
poweredByLabel.textColor .tertiaryLabel
poweredByLabel.textAlignment .center
stack (arrangedSubviews: [titleLabel, subtitleLabel, signInButton, poweredByLabel])
stack.axis .vertical
stack.spacing
stack.translatesAutoresizingMaskIntoConstraints
view.addSubview(stack)
.activate([
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor),
stack.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: ),
stack.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: ),
])
}
() {
.shared.login(from: ) { result
.main.async {
result {
.success:
dashboard ()
.navigationController.pushViewController(dashboard, animated: )
.failure( error):
alert (title: , message: error.localizedDescription, preferredStyle: .alert)
alert.addAction((title: , style: .default))
.present(alert, animated: )
}
}
}
}
}
Step 3: Test the Implementation
- Run on a simulator or device.
- Tap "Sign In with MojoAuth".
- A browser/ASWebAuthenticationSession opens to the MojoAuth Hosted Login Page.
- Authenticate using any available method.
- Verify you are redirected back to the app and into the Dashboard.
3. Additional Considerations
- Security: PKCE is handled automatically by AppAuth. Never embed Client Secrets in mobile apps.
- Token Storage: Use the iOS Keychain to store tokens securely.
- SwiftUI: For SwiftUI apps, wrap the
OIDAuthState.authState(byPresenting:) call in a coordinator or use ASWebAuthenticationSession directly.
4. Support