A Swift package that provides a simple way to implement passkey authentication in your iOS and macOS applications.
- iOS 16.0+ / macOS 13.0+
- Xcode 14.0+
- Swift 5.9+
Add PasskeyAuth to your project using Swift Package Manager:
- In Xcode, select your project in the Project Navigator
- Select your target
- Select the "Package Dependencies" tab
- Click the "+" button
- Enter the repository URL:
https://github.com/ephemeraHQ/PasskeyAuth.git
- Click "Add Package"
First, create a PasskeyConfiguration
instance with your server details:
// Basic configuration with default endpoints
let configuration = PasskeyConfiguration(
baseURL: URL(string: "https://your-server.com")!,
rpID: "your-server.com"
)
// Configuration with custom endpoint paths
let configuration = PasskeyConfiguration(
baseURL: URL(string: "https://your-server.com")!,
rpID: "your-server.com",
endpoints: PasskeyEndpoints(
registerChallenge: "/api/v1/auth/register-challenge",
loginChallenge: "/api/v1/auth/login-challenge",
registerPasskey: "/api/v1/auth/register",
loginPasskey: "/api/v1/auth/login"
)
)
// Configuration with custom base path for endpoints
let configuration = PasskeyConfiguration(
baseURL: URL(string: "https://your-server.com")!,
rpID: "your-server.com",
endpoints: PasskeyEndpoints(basePath: "/api/v1/auth")
)
// Configuration with certificate pinning
let configuration = PasskeyConfiguration(
baseURL: URL(string: "https://your-server.com")!,
rpID: "your-server.com",
pinnedCertificates: [certificateData] // Add your server's certificate data
)
Create a presentation context provider that conforms to PasskeyPresentationContextProvider
:
class MyPresentationContextProvider: PasskeyPresentationContextProvider {
let presentationAnchor: ASPresentationAnchor
init(window: UIWindow) {
self.presentationAnchor = window
}
}
Then create a PasskeyAuth
instance with your configuration.
let presentationProvider = MyPresentationContextProvider(window: window)
let passkeyAuth = PasskeyAuth(
configuration: configuration,
logger: PasskeyAuthLogger.Logger.shared // Optional: Provide a custom logger
)
For SwiftUI apps, you can create a provider that uses the current window:
class SwiftUIPresentationContextProvider: PasskeyPresentationContextProvider {
let presentationAnchor: ASPresentationAnchor
init() {
// Get the current window from the scene
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = windowScene.windows.first else {
fatalError("No window found")
}
self.presentationAnchor = window
}
}
Set the presentation provider:
VStack {
...
}
.onAppear {
PasskeyError.setDebugMode(true)
Task { @MainActor in
let presentationProvider = SwiftUIPresentationProvider()
await passkeyAuth.setPresentationContextProvider(presentationProvider)
}
}
To register a new passkey:
Task {
do {
let response = try await passkeyAuth.registerPasskey(displayName: "My Passkey")
if response.success {
print("Passkey registered successfully")
print("Token:", response.token)
} else {
print("Registration failed")
}
} catch {
print("Failed to register passkey:", error.localizedDescription)
}
}
To login with a passkey:
Task {
do {
let response = try await passkeyAuth.loginWithPasskey()
if response.success {
print("Logged in successfully")
print("Token:", response.token)
} else {
print("Login failed")
}
} catch {
print("Failed to login:", error.localizedDescription)
}
}
PasskeyAuth includes a built-in logging system that helps with debugging and monitoring. The logger is configurable and production-safe:
// Get the shared logger instance
let logger = PasskeyAuthLogger.Logger.shared
// Configure minimum log level
logger.minimumLogLevel = .info // Only show info and above
// Use different log levels
logger.debug("Detailed debug information")
logger.info("General information")
logger.warning("Warning message")
logger.error("Error message")
// Create a production-safe logger
let productionLogger = PasskeyAuthLogger.Logger(isProduction: true)
The logger includes the following features:
- Different log levels (debug, info, warning, error)
- Configurable minimum log level
- Production mode that prevents logging sensitive data
- Timestamp and file information in log messages
- Emoji indicators for different log levels
- Debug-only logging by default
In production mode, the logger automatically filters out messages containing sensitive information like:
- Tokens
- Certificates
- Keys
- Passwords
Additional debug info can be included in errors with this option:
PasskeyError.setDebugMode(true) // should not be used in production
The package provides a PasskeyError
enum that covers common error cases:
invalidURL
: The URL is invalidnoData
: No data was received from the serverinvalidChallenge
: The challenge received from the server is invalidauthenticationFailed
: The authentication failedregistrationFailed
: The registration failedauthenticationInProgress
: Another authentication attempt is already in progressrateLimit
: The request was rate limited by the servernetworkError
: Network connectivity errorserverError
: Server error with status codejsonParsingError
: JSON parsing errorconfigurationError
: Configuration error
Each error case includes a user-friendly error description and recovery suggestion.
The PasskeyAuth
class is implemented as an actor, ensuring thread safety for all operations. All public methods can be called from any thread, and UI-related operations are automatically dispatched to the main thread.
PasskeyAuth supports certificate pinning for enhanced security. You can provide your server's certificate data when creating the configuration:
// Load your server's certificate
guard let certificateURL = Bundle.main.url(forResource: "server", withExtension: "cer"),
let certificateData = try? Data(contentsOf: certificateURL) else {
fatalError("Failed to load certificate")
}
// Create configuration with certificate pinning
let configuration = PasskeyConfiguration(
baseURL: URL(string: "https://your-server.com")!,
rpID: "your-server.com",
pinnedCertificates: [certificateData]
)
The package will verify that the server's certificate matches your pinned certificate during all network requests.
The package includes an example app (PasskeyAuthExample
) that demonstrates how to implement passkey authentication in a real iOS application. To run the example app:
- Clone the repository
- Create a
.env
file in the root directory with your configuration:API_BASE_URL=https://your-server.com RP_ID=your-server.com
- Generate the required configuration files:
This will:
make generate
- Generate
Secrets.swift
from your.env
file - Generate the app site association entitlements file
- Generate
The Makefile provides several useful commands:
make secrets
: Generate only the Secrets.swift filemake entitlements
: Generate only the entitlements filemake generate
: Generate all configuration filesmake help
: Show all available commands
PasskeyAuth is available under the MIT license. See the LICENSE file for more info.