Overview

This guide outlines how to implement social login (OAuth) with the ParaSwift SDK. It covers setup, authentication flow, and handling both new and existing user scenarios with providers like Google, Apple, and Discord.

Prerequisites

Prerequisites

To use Para, you need an API key. This key authenticates your requests to Para services and is essential for integration.

Don’t have an API key yet? Request access to the Developer Portal to create API keys, manage billing, teams, and more.

You must have the ParaSwift SDK installed and configured in your project. If you haven’t done this yet, please refer to our Quick Start Guide.

Environment Setup

Before implementing social login, ensure your environment is properly configured:

  1. Add the webAuthenticationSession environment value in your view
  2. Add the authorizationController environment value for handling passkey operations after social login
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@Environment(\.authorizationController) private var authorizationController

Implementing Social Login

Setup

Create a view that will handle the social login process:

import SwiftUI
import ParaSwift
import AuthenticationServices

struct SocialLoginView: View {
    @EnvironmentObject var paraManager: ParaManager
    
    @Environment(\.webAuthenticationSession) private var webAuthenticationSession
    @Environment(\.authorizationController) private var authorizationController
    
    @State private var email = ""
    @State private var error: Error?
}

Initiating OAuth Authentication

To begin the OAuth flow with a specific provider, call the oAuthConnect method:

private func login(provider: OAuthProvider) {
    Task {
        do {
            let email = try await paraManager.oAuthConnect(provider: provider, webAuthenticationSession: webAuthenticationSession)
            handleLogin(email: email)
        } catch {
            self.error = error
        }
    }
}

Handling Authentication Result

After a successful OAuth connection, you’ll need to handle both new and existing user scenarios:

private func handleLogin(email: String) {
    Task {
        self.email = email
        let userExists = try await paraManager.checkIfUserExists(email: email)
        
        if userExists {
            // User exists - complete login with passkey
            try await paraManager.login(authorizationController: authorizationController, authInfo: EmailAuthInfo(email: email))
            // Navigate to authenticated area of your app
        } else {
            // New user - create account
            try await paraManager.createUser(email: email)
            // Navigate to verification or onboarding view
        }
    }
}

Creating Social Login Buttons

Here’s an example of how to create buttons for different OAuth providers:

var body: some View {
    VStack {
        // Google Login Button
        Button {
            login(provider: .google)
        } label: {
            HStack(spacing: 15) {
                Image("google")
                    .resizable()
                    .frame(width: 24, height: 24)
                Text("Login with Google")
                    .fontWeight(.semibold)
            }
            .frame(maxWidth: .infinity)
        }
        .buttonStyle(.borderedProminent)
        .controlSize(.large)
        .tint(.primary)
        .foregroundStyle(.background)
        
        // Discord Login Button
        Button {
            login(provider: .discord)
        } label: {
            HStack(spacing: 15) {
                Image("discord")
                    .resizable()
                    .frame(width: 24, height: 20)
                Text("Login with Discord")
                    .fontWeight(.semibold)
            }
            .frame(maxWidth: .infinity)
        }
        .buttonStyle(.borderedProminent)
        .controlSize(.large)
        .tint(Color(uiColor: UIColor(rgb: 0x5865F2)))
        
        // Apple Login Button
        Button {
            login(provider: .apple)
        } label: {
            HStack(spacing: 15) {
                Image("apple")
                    .resizable()
                    .frame(width: 24, height: 24)
                Text("Login with Apple")
                    .fontWeight(.semibold)
            }
            .frame(maxWidth: .infinity)
        }
        .buttonStyle(.borderedProminent)
        .controlSize(.large)
    }
    .padding()
}

Available OAuth Providers

The ParaSwift SDK supports the following OAuth providers:

  • Google (.google)
  • Apple (.apple)
  • Discord (.discord)

Complete Example

Here’s a complete example of a social login view:

import SwiftUI
import ParaSwift
import AuthenticationServices

struct SocialLoginView: View {
    @EnvironmentObject var paraManager: ParaManager
    @State private var navigationState: NavigationState = .idle
    
    @Environment(\.webAuthenticationSession) private var webAuthenticationSession
    @Environment(\.authorizationController) private var authorizationController
    
    @State private var email = ""
    @State private var error: Error?
    @State private var showError = false
    
    enum NavigationState {
        case idle
        case verification
        case home
    }
    
    private func login(provider: OAuthProvider) {
        Task {
            do {
                let email = try await paraManager.oAuthConnect(provider: provider, webAuthenticationSession: webAuthenticationSession)
                await handleLogin(email: email)
            } catch {
                self.error = error
                self.showError = true
            }
        }
    }
    
    private func handleLogin(email: String) async {
        do {
            self.email = email
            let userExists = try await paraManager.checkIfUserExists(email: email)
            
            if userExists {
                try await paraManager.login(authorizationController: authorizationController, authInfo: EmailAuthInfo(email: email))
                navigationState = .home
            } else {
                try await paraManager.createUser(email: email)
                navigationState = .verification
            }
        } catch {
            self.error = error
            self.showError = true
        }
    }
    
    var body: some View {
        VStack {
            Button {
                login(provider: .google)
            } label: {
                HStack(spacing: 15) {
                    Image("google")
                        .resizable()
                        .frame(width: 24, height: 24)
                    Text("Login with Google")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)
            .tint(.primary)
            .foregroundStyle(.background)
            
            Button {
                login(provider: .discord)
            } label: {
                HStack(spacing: 15) {
                    Image("discord")
                        .resizable()
                        .frame(width: 24, height: 20)
                    Text("Login with Discord")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)
            .tint(Color(uiColor: UIColor(rgb: 0x5865F2)))
            
            Button {
                login(provider: .apple)
            } label: {
                HStack(spacing: 15) {
                    Image("apple")
                        .resizable()
                        .frame(width: 24, height: 24)
                    Text("Login with Apple")
                        .fontWeight(.semibold)
                }
                .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)
        }
        .alert("Connection Error", isPresented: $showError) {
            Button("OK", role: .cancel) { }
        } message: {
            Text(error?.localizedDescription ?? "Unknown error occurred")
        }
        .navigationDestination(isPresented: Binding(
            get: { navigationState == .verification },
            set: { if !$0 { navigationState = .idle } }
        )) {
            VerifyEmailView(email: email)
        }
        .navigationDestination(isPresented: Binding(
            get: { navigationState == .home },
            set: { if !$0 { navigationState = .idle } }
        )) {
            HomeView()
        }
        .padding()
        .navigationTitle("Social Login")
    }
}

// Helper extension for UIColor from hex
extension UIColor {
    convenience init(rgb: UInt) {
        self.init(
            red: CGFloat((rgb & 0xFF0000) >> 16) / 255.0,
            green: CGFloat((rgb & 0x00FF00) >> 8) / 255.0,
            blue: CGFloat(rgb & 0x0000FF) / 255.0,
            alpha: CGFloat(1.0)
        )
    }
}

Next Steps

After implementing social login, you might want to:

  1. Set up email verification for new users
  2. Implement secure storage for session management
  3. Add biometric authentication as an additional security layer