The official iOS SDK for integrating Flida authentication into your iOS applications.
- In Xcode, go to File > Add Package Dependencies...
- Enter the repository URL:
https://github.com/flida-dev/ios-sdk - Select the version you want to use.
Add the FlidaAuthHost key to your Info.plist file:
<key>FlidaAuthHost</key>
<string>YOUR_CLIENT_ID.api.flida.dev</string>Format: {clientId}.api.{domain}
Examples:
019b650a-156e-77f3-ad1d-df2e2d8c2a5c.api.flida.dev019b650a-156e-77f3-ad1d-df2e2d8c2a5c.api.flida.ru
The SDK automatically derives from FlidaAuthHost:
- Client ID — extracted from the first part
- API Endpoint —
https://api.{domain} - Auth Endpoint —
https://{domain} - Redirect URI —
flida{clientId}://auth
Add a URL scheme to handle the OAuth callback. In your Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>flida{YOUR_CLIENT_ID}</string>
</array>
</dict>
</array>The SDK initializes automatically from Info.plist when you first access FlidaIDSDK.shared. No explicit initialization needed.
To start the login flow, call signIn. You need to provide a ASWebAuthenticationPresentationContextProviding (usually your view controller).
import AuthenticationServices
import FlidaIDSDK
class ViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
func signIn() {
FlidaIDSDK.shared.signIn(
presenting: self,
scopes: ["openid", "name", "e-mail-address", "phone-number"]
) { result in
switch result {
case .success(let tokenResponse):
print("Access Token: \(tokenResponse.token.accessToken)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
}
// ASWebAuthenticationPresentationContextProviding
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return self.view.window!
}
}Once authorized, you can fetch user details:
FlidaIDSDK.shared.getUserInfo(accessToken: accessToken) { result in
switch result {
case .success(let user):
print("User Name: \(user.name)")
print("User ID: \(user.id)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}To refresh the access token:
FlidaIDSDK.shared.refreshTokens(refreshToken: refreshToken) { result in
switch result {
case .success(let tokenResponse):
print("New Access Token: \(tokenResponse.token.accessToken)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}FlidaIDSDK.shared.logout()The SDK provides a Combine-based event stream for reactive programming. Subscribe to events to get notified about authentication state changes.
| Event | Description |
|---|---|
signedIn(user:accessToken:) |
User successfully signed in |
signInFailed(error:) |
Sign in failed |
tokensRefreshed(accessToken:) |
Tokens refreshed successfully |
tokenRefreshFailed(error:) |
Token refresh failed |
loggedOut(reason:) |
User was logged out |
userInfoFetched(user:) |
User info fetched successfully |
userInfoFetchFailed(error:) |
User info fetch failed |
| Reason | Description |
|---|---|
.userInitiated |
User called logout() explicitly |
.sessionExpired |
Refresh token expired (404) |
.unauthorized |
Server returned 401 on token refresh |
import Combine
import FlidaIDSDK
class AuthManager {
private var cancellables = Set<AnyCancellable>()
init() {
FlidaIDSDK.shared.events.events
.sink { [weak self] event in
self?.handleEvent(event)
}
.store(in: &cancellables)
}
private func handleEvent(_ event: FlidaEvent) {
switch event {
case .signedIn(let user, let accessToken):
print("User signed in: \(user?.name ?? "Unknown")")
case .signInFailed(let error):
print("Sign in failed: \(error.localizedDescription)")
case .tokensRefreshed(let accessToken):
print("Tokens refreshed")
case .tokenRefreshFailed(let error):
print("Token refresh failed: \(error.localizedDescription)")
case .loggedOut(let reason):
switch reason {
case .userInitiated:
print("User logged out")
case .sessionExpired:
print("Session expired")
case .unauthorized:
// 401 on refresh - redirect to login
print("Session invalid, please sign in again")
}
case .userInfoFetched(let user):
print("User info: \(user.name)")
case .userInfoFetchFailed(let error):
print("Failed to get user info: \(error.localizedDescription)")
}
}
}All SDK methods return typed FlidaError for precise error handling:
FlidaIDSDK.shared.signIn(presenting: self, scopes: ["openid"]) { result in
switch result {
case .success(let response):
// Handle success
case .failure(let error):
switch error {
case .userCancelled:
// User cancelled - no need to show error
break
case .notInitialized:
print("Configure FlidaAuthHost in Info.plist")
case .unauthorized:
print("Token expired, please sign in again")
case .networkError(let underlyingError):
print("Network error: \(underlyingError)")
default:
print("Error: \(error.localizedDescription)")
}
}
}| Error | Description |
|---|---|
notInitialized |
SDK not configured (missing FlidaAuthHost) |
userCancelled |
User cancelled authentication |
invalidCallbackURL |
Invalid callback URL received |
stateMismatch |
OAuth state mismatch (possible CSRF) |
noAuthorizationCode |
No authorization code in callback |
pkceGenerationFailed |
Failed to generate PKCE challenge |
oauthError(String) |
OAuth error from server |
unauthorized |
401 - Access token expired |
forbidden(String?) |
403 - Access denied |
refreshTokenExpired |
Refresh token expired |
tokenExchangeFailed(String?) |
Token exchange failed |
networkError(Error) |
Network request failed |
serverError(statusCode:message:) |
Server returned an error |
decodingError(Error) |
Failed to decode response |
noData |
No data received from server |
The SDK includes a built-in logging system with configurable log levels.
| Level | Description |
|---|---|
.none |
Logging disabled (default) |
.error |
Only errors |
.warning |
Errors and warnings |
.info |
Errors, warnings, and info messages |
.debug |
All above + HTTP request/response info |
.verbose |
All messages including HTTP bodies |
// Enable debug logging
FlidaIDSDK.shared.setLogLevel(.debug)
// Or for maximum detail
FlidaIDSDK.shared.setLogLevel(.verbose)
⚠️ Security Note: At.verboselevel, sensitive data is automatically masked, but it's recommended to only use verbose logging during development.