Skip to content

rxtech-lab/RxAuthSwift

Repository files navigation

RxAuthSwift

OAuth 2.0 authentication library for iOS and macOS with PKCE support and customizable SwiftUI sign-in UI.

Requirements

  • iOS 18+ / macOS 15+
  • Swift 6.2+
  • Xcode 16+

Installation

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/nicktrienenern/RxAuthSwift.git", from: "1.0.0"),
]

Then add the targets you need:

.target(
    name: "YourApp",
    dependencies: [
        "RxAuthSwift",      // Core OAuth logic
        "RxAuthSwiftUI",    // Sign-in UI components (optional)
    ]
)

Usage

1. Configure OAuth

import RxAuthSwift

let config = RxAuthConfiguration(
    issuer: "https://auth.example.com",
    clientID: "your-client-id",
    redirectURI: "yourapp://callback",
    scopes: ["openid", "profile", "email"]
)

Endpoint paths default to /api/oauth/authorize, /api/oauth/token, /api/oauth/userinfo but can be overridden:

let config = RxAuthConfiguration(
    issuer: "https://auth.example.com",
    clientID: "your-client-id",
    redirectURI: "yourapp://callback",
    authorizePath: "/oauth/authorize",
    tokenPath: "/oauth/token",
    userInfoPath: "/oauth/me"
)

For native macOS sign-in, RxSignInView renders username/password fields instead of launching a browser. Password sign-in posts an OAuth password-grant request directly to nativePasswordTokenPath or tokenPath when no native override is provided:

let config = RxAuthConfiguration(
    issuer: "https://auth.example.com",
    clientID: "your-client-id",
    redirectURI: "yourapp://callback",
    nativePasswordTokenPath: "/api/oauth/token",
    nativeSignupPath: "/api/oauth/signup"
)

The signup endpoint receives JSON with client_id, username, password, optional name, and scope, and should return the same token JSON as the OAuth token endpoint.

Passkey sign-in and signup are enabled when the matching endpoint pairs are configured:

let config = RxAuthConfiguration(
    issuer: "https://auth.example.com",
    clientID: "your-client-id",
    redirectURI: "yourapp://callback",
    passkeyChallengePath: "/api/passkeys/authentication/options",
    passkeyVerificationPath: "/api/passkeys/authentication/verify",
    passkeyRegistrationChallengePath: "/api/passkeys/registration/options",
    passkeyRegistrationVerificationPath: "/api/passkeys/registration/verify",
    passkeyRelyingPartyIdentifier: "auth.example.com"
)

The authentication challenge endpoint should return a base64url WebAuthn challenge, optional requestID, optional relying party identifier, and optional allowed credential IDs. The registration challenge endpoint should return a base64url challenge and user ID, plus optional requestID, username, and relying party identifier. Verification endpoints receive the platform passkey assertion or registration payload and return the same token JSON as the OAuth token endpoint.

2. Create OAuthManager

let authManager = OAuthManager(configuration: config)

You can inject custom token storage:

let authManager = OAuthManager(
    configuration: config,
    tokenStorage: MyCustomTokenStorage()
)

3. Check Existing Auth on Launch

@main
struct MyApp: App {
    @State private var authManager = OAuthManager(configuration: config)

    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    await authManager.checkExistingAuth()
                }
        }
    }
}

4. Show Sign-In UI

Simple (with appearance customization):

import RxAuthSwiftUI

RxSignInView(
    manager: authManager,
    appearance: RxSignInAppearance(
        icon: .image(Image("MyLogo")),
        title: "Welcome",
        subtitle: "Sign in to continue",
        signInButtonTitle: "Get Started",
        accentColor: .purple,
        secondaryColor: .pink
    )
)

With callbacks:

RxSignInView(
    manager: authManager,
    onAuthSuccess: {
        // Navigate to home, fetch user data, etc.
    },
    onAuthFailed: { error in
        // Log error, show custom alert, etc.
    }
)

Advanced (fully custom header with ViewBuilder):

RxSignInView(
    manager: authManager,
    onAuthSuccess: { print("Signed in!") },
    onAuthFailed: { error in print("Failed: \(error)") }
) {
    VStack {
        Image("Logo")
            .resizable()
            .frame(width: 100, height: 100)
        Text("My App")
            .font(.largeTitle.bold())
    }
}

5. React to Auth State

switch authManager.authState {
case .unknown:
    ProgressView()
case .unauthenticated:
    RxSignInView(manager: authManager)
case .authenticated:
    HomeView(user: authManager.currentUser)
}

6. Logout

await authManager.logout()

7. Listen for Session Expiry

NotificationCenter.default.addObserver(
    forName: .rxAuthSessionExpired,
    object: nil,
    queue: .main
) { _ in
    // Handle session expiry
}

Icon Options

The SignInIcon enum supports:

.systemImage("lock.shield.fill")          // SF Symbol
.image(Image("MyLogo"))                    // SwiftUI Image
.assetImage("AppIcon", Bundle.main)        // Asset catalog
.none                                       // No icon

Custom Token Storage

Implement TokenStorageProtocol for custom storage backends:

public protocol TokenStorageProtocol: Sendable {
    func saveAccessToken(_ token: String) throws
    func getAccessToken() -> String?
    func deleteAccessToken() throws
    func saveRefreshToken(_ token: String) throws
    func getRefreshToken() -> String?
    func deleteRefreshToken() throws
    func saveExpiresAt(_ date: Date) throws
    func getExpiresAt() -> Date?
    func isTokenExpired() -> Bool
    func clearAll() throws
}

Glass Effect Support

UI components automatically use .glassEffect on iOS 26+ / macOS 26+ and fall back to .ultraThinMaterial / .borderedProminent on older platforms.

Building

# macOS
bash scripts/build-macos.sh

# iOS
bash scripts/build-ios.sh

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors