Skip to content
This repository was archived by the owner on Jan 2, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 47 additions & 22 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Target for all authentication related operations.
- [x] Authentication by Apple Id
- [x] Error Handling
- [x] Link Anonymous and authenticated Accounts
- [ ] Authentication by Email and Password
- [x] Authentication by Email and Password
- [x] Sign out and Deleting an Account
- [x] UIKit-View for handling SignInWithApple-Requests

Expand All @@ -208,7 +208,7 @@ AuthenticationManager.email = "john.doe@apple.com" // email is overwritten and c


##### Configuration
By default the `AuthenticationManager` is using Sign In With Apple and allows also anonymous authentication. If you want to disable it, you can use a custom configuration object. With that the AuthenticationManager needs to be initialized on App Start.
By default, the `AuthenticationManager` allows three authentication methods: Sign in with Email and Password, by using the Apple ID and anonymous login. If you want to restrict the providers, you can use a custom configuration object. With that, the AuthenticationManager needs to be initialized on App Start.

You can also link a repository where you manage your users details. If you subclass the `UserRepositoryProtocol`your user's details with the user details you get during the authentication process.

Expand All @@ -227,7 +227,7 @@ struct MyApp: App {
FirebaseApp.configure()

var authenticationConfiguration = Configuration()
authenticationConfiguration.allowAnonymousUsers = false
authenticationConfiguration.authProvider = [.signInWithApple, .anonymous, .emailPassword]
authenticationConfiguration.userRepository = MyRepository.shared
AuthenticationManager.setup(authenticationConfiguration)
}
Expand All @@ -245,7 +245,7 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
FirebaseApp.configure()

var authenticationConfiguration = Configuration()
authenticationConfiguration.allowAnonymousUsers = false
authenticationConfiguration.authProvider = [.signInWithApple, .anonymous, .emailPassword]
authenticationConfiguration.userRepository = MyRepository.shared
AuthenticationManager.setup(authenticationConfiguration)

Expand All @@ -254,11 +254,40 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau
```


##### Authenticate with Email and Password
```Swift
// Create Account
AuthenticationManager.signUpWithEmail(email: "john.doe@apple.com", password: "123") { error in
if let error {
// Check detailed Error Response
} else {
// Account was created and User logged in successfully
}
}

// Login
AuthenticationManager.loginWithEmail(email: "john.doe@apple.com", password: "123") { error in
if let error {
// Check detailed Error Response
} else {
// Authentication was successful
}
}


// Update User Information
AuthenticationManager.resetPassword(for: "john.doe@apple.com")
AuthenticationManager.updateMail(currentPassword: "123", newMail: "jane.doe@apple.com") { error in }
AuthenticationManager.updatePassword(currentPassword: "123", newPassword: "456") { error in }
```


##### Authenticate by using Apple Id

The Authentication Manager controls the whole authentication flow and returns you the handled error without any further work.
The Authentication Manager controls the whole authentication flow and returns the handled error without any further work.

###### SwiftUI:

```Swift
import FirebaseAuthenticationManager
import AuthenticationServices
Expand Down Expand Up @@ -391,11 +420,22 @@ AuthenticationManager.signOut { error in
##### Delete Account

You can delete a user's account in Firebase.
This can require a reauthentication before executing this method.
This can require a reauthentication before executing this method which is done automatically for users who signed in with email and password.

If your user signed in with its Apple-Account, this requires involving the UI to receive the status of authentication. We don't handle this error (yet), so we advise you to execute an additional login manually before accessing this function.

```Swift
// Account is related to Email and Password
AuthenticationManager.deleteAccount(currentPassword: "123") { error in
if let error {
// Check detailed Error Response
} else {
// Deletion of Account was successful
// If you stored your user's data in a database, don't forget to implement deleting it separately.
}
}

// Account is related to Apple ID
AuthenticationManager.deleteAccount { error in
if let error {
// Check detailed Error Response
Expand Down Expand Up @@ -443,7 +483,7 @@ class UserRepository: UserRepositoryProtocol {

It is recommended to check a user's authorization status on app start, or possibly before sensitive transactions, since these could be changed outside of your app.

You can simplify this Authorization-Flow by implementing the `UserRepositoryProtocol`. We provide two functions, choose the one you need according to your defined Authentication Providers.
You can simplify this Authorization-Flow by implementing the `UserRepositoryProtocol`.

If you stored your users details in a remote database, you might want to fetch it after the authorization was successful.
For this you can implement the `fetchCurrentUser`-Method, which is executed automatically on successful authorization.
Expand All @@ -453,21 +493,6 @@ class UserRepository: UserRepositoryProtocol {

// Custom Function for checking Authorization -> can be called anywhere in your app where you need it
func checkAuthState() {
// Depending on your Authentication Provider choose

// (a) if you support anonymous users
self.checkAuthorizationWithAnonymousUser { error in
if let error {
// Handle Error

// We recommend signing out
AuthenticationManager.signOut { _ in }
}

// if no error occured fetchCurrentUser() is called automatically
}

// (b) if you support only Sign In With Apple
self.checkAuthorization { error in
if let error {
// Handle Error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
//
// Authentication.swift
// Anonymous.swift
//
//
// Created by Anna Münster on 22.09.22.
// Created by Anna Münster on 20.01.23.
//

import Foundation

extension AuthenticationManager {
public static func authenticateAnonymously(completion: @escaping(Error?) -> Void) {
guard configuration.allowAnonymousUsers else {
guard configuration.authProvider.contains(.anonymous) else {
completion(AuthenticationError.configuration)
return
}
Expand All @@ -24,10 +24,12 @@ extension AuthenticationManager {
auth.signInAnonymously { _, error in
if let error {
completion(AuthenticationError.firebase(error: error))
} else {
print("Created account for anonymous user with id \(userId ?? "")")
completion(nil)
return
}

print("Created account for anonymous user with id \(userId ?? "")")
self.currentProvider = .anonymous
completion(nil)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//
// EmailPassword.swift
//
//
// Created by Anna Münster on 12.01.23.
//

import Foundation
import FirebaseAuth

extension AuthenticationManager {

public static func signUpWithEmail(email: String, password: String, completion: @escaping (Error?) -> Void) {
guard configuration.authProvider.contains(.emailPassword) else {
completion(AuthenticationError.configuration)
return
}

auth.createUser(withEmail: email, password: password) { _, error in
if let error {
completion(AuthenticationError.firebase(error: error))
return
}

print("Welcome user with id \(userId ?? "")")
self.currentProvider = .emailPassword
completion(nil)
}
}

public static func loginWithEmail(email: String, password: String, completion: @escaping (Error?) -> Void) {
guard configuration.authProvider.contains(.emailPassword) else {
completion(AuthenticationError.configuration)
return
}

auth.signIn(withEmail: email, password: password) { _, error in
if let error {
completion(AuthenticationError.firebase(error: error))
return
}

print("Welcome user with id \(userId ?? "")")
self.currentProvider = .emailPassword
completion(nil)
}
}

public static func resetPassword(for email: String) {
auth.sendPasswordReset(withEmail: email) { error in
print("Reset mail was sent successfully to \(email)")
}
}

private static func reauthenticateWithEmail(password: String, completion: @escaping (Error?) -> Void) {
guard let currentUser, let email = currentUser.email else {
completion(AuthenticationError.unauthorized)
return
}

let credential = EmailAuthProvider.credential(withEmail: email, password: password)

currentUser.reauthenticate(with: credential) { _, error in
if let error {
completion(AuthenticationError.firebase(error: error))
return
}

completion(nil)
}
}

public static func updateMail(currentPassword: String, newMail: String, completion: @escaping (Error?) -> Void) {
guard let currentUser else {
completion(AuthenticationError.unauthorized)
return
}

reauthenticateWithEmail(password: currentPassword) { error in
if let error {
completion(error)
return
}

currentUser.updateEmail(to: newMail)
completion(nil)
}
}

public static func updatePassword(currentPassword: String, newPassword: String, completion: @escaping (Error?) -> Void) {
guard let currentUser else {
completion(AuthenticationError.unauthorized)
return
}

reauthenticateWithEmail(password: currentPassword) { error in
currentUser.updatePassword(to: newPassword) { error in
completion(error)
return
}

completion(nil)
}
}

public static func deleteAccount(currentPassword: String, completion: @escaping (Error?) -> Void) {
guard let currentUser else {
completion(AuthenticationError.unauthorized)
return
}

reauthenticateWithEmail(password: currentPassword) { error in
currentUser.delete { error in
if let error {
completion(AuthenticationError.firebase(error: error))
return
}

self.currentProvider = nil

completion(nil)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ extension AuthenticationManager {

This cannot be used to ask for permission. It is solely to check for the permission status.
*/
static func checkAuthorizationState(completion: @escaping (Result<Any?, AuthorizationError>) -> Void) {
public static func checkAuthorizationState(completion: @escaping (Result<Bool?, Error>) -> Void) {
guard let authorizationKey = authorizationKey, !authorizationKey.isEmpty else {
completion(.failure(.credentialState(description: "Missing Key from User Defaults", error: nil)))
completion(.failure(AuthorizationError.credentialState(description: "Missing Key from User Defaults", error: nil)))
return
}

Expand All @@ -36,7 +36,7 @@ extension AuthenticationManager {
case .authorized:
completion(.success(nil))
default:
completion(.failure(.credentialState(description: credentialState.description, error: error)))
completion(.failure(AuthorizationError.credentialState(description: credentialState.description, error: error)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ extension AuthenticationManager {
checkAuthorizationResult(authResult) { result in
switch result {
case .success(let credential):
self.currentProvider = .signInWithApple
updateUserInfo(credential: credential, completion: completion)
case .failure(let error):
self.handleError(error, completion: completion)
Expand Down Expand Up @@ -88,13 +89,17 @@ extension AuthenticationManager {
static func authenticate(credential: AuthCredential, completion: @escaping (Result<Bool, Error>) -> Void) {

if let currentUser {
// Some user has used the app, either anonymously or with Apple

if !userIsAuthenticated {
// anonymous account is linked to new created one
currentUser.link(with: credential, completion: handleResult)
} else {
// Account is reauthenticated to perform some security-sensitive actions (deleting account for now, later may be password change or mail-adress-change)
currentUser.reauthenticate(with: credential, completion: handleResult)
}
} else {
// Only happens if no user has ever used the app on the device
auth.signIn(with: credential, completion: handleResult)
}

Expand Down
22 changes: 14 additions & 8 deletions Sources/AuthenticationManager/Authentication/SignOut.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Lifecycle.swift
// SignOut.swift
//
//
// Created by Anna Münster on 22.09.22.
Expand All @@ -10,17 +10,22 @@ import Foundation
extension AuthenticationManager {
/**
Sign out from Firebase on the device and remove the authorization key for sign in with Apple.

The providerId is checked because if there is none, the user is either not signed in or is anonymous.
*/
public static func signOut(completion: @escaping (Error?) -> Void) {
guard let providerId = auth.currentUser?.providerData.first?.providerID,
self.providerId == providerId
else {
completion(AuthorizationError.providerId)
return
if userIsAuthenticated, currentProvider == .signInWithApple {
guard let providerId = auth.currentUser?.providerData.first?.providerID,
self.providerId == providerId
else {
completion(AuthorizationError.providerId)
return
}
}

do {
try auth.signOut()
self.currentProvider = nil
removeAuthorizationKey()
completion(nil)
} catch let signOutError {
Expand All @@ -35,8 +40,8 @@ extension AuthenticationManager {
- Attention: Database Triggers need to be implemented to remove all associated data.
*/
public static func deleteAccount(completion: @escaping(Error?) -> Void) {
guard let currentUser = auth.currentUser else {
completion(nil)
guard let currentUser else {
completion(AuthenticationError.unauthorized)
return
}

Expand All @@ -46,6 +51,7 @@ extension AuthenticationManager {
return
}

self.currentProvider = nil
removeAuthorizationKey()
completion(nil)
}
Expand Down
Loading