Files
Nearfield_Friends/Proximity/Services/Auth/GoogleAuthProvider.swift

74 lines
2.8 KiB
Swift

import Foundation
import AuthenticationServices
/// Google OAuth (via ASWebAuthenticationSession) an alternative account
/// identity path. Google is a true OAuth provider, unlike Instagram/X which
/// are not suitable for login (see AuthService notes).
final class GoogleAuthProvider: NSObject, AuthProvider {
var providerID: AuthProviderID { .google }
private let clientID = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
private let redirectURI = "com.proximity.app:/oauth2redirect"
func signIn() async throws -> AuthResult {
// Build the Google OAuth2 authorization URL.
var components = URLComponents(string: "https://accounts.google.com/o/oauth2/v2/auth")!
components.queryItems = [
URLQueryItem(name: "client_id", value: clientID),
URLQueryItem(name: "redirect_uri", value: redirectURI),
URLQueryItem(name: "response_type", value: "code"),
URLQueryItem(name: "scope", value: "openid email profile"),
URLQueryItem(name: "nonce", value: UUID().uuidString)
]
guard let url = components.url else {
throw AuthError.invalidCredential
}
// Present the ASWebAuthenticationSession and await the callback.
let callbackURL = try await withCheckedThrowingContinuation {
(continuation: CheckedContinuation<URL, Error>) in
let session = ASWebAuthenticationSession(
url: url,
callbackURLScheme: "com.proximity.app"
) { callback, error in
if let error {
continuation.resume(throwing: error)
} else if let callback {
continuation.resume(returning: callback)
} else {
continuation.resume(throwing: AuthError.invalidCredential)
}
}
session.presentationContextProvider = self
session.start()
}
// Extract the authorization code from the callback.
guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false),
let code = components.queryItems?.first(where: { $0.name == "code" })?.value else {
throw AuthError.invalidCredential
}
// In production: exchange `code` for an ID token at our backend,
// which verifies it with Google. Here we pass the code through.
return AuthResult(
providerID: .google,
idToken: code,
nonce: nil,
profile: ProviderProfile()
)
}
func signOut() async throws {
// Revoke handled by backend.
}
}
extension GoogleAuthProvider: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
ASPresentationAnchor()
}
}