SwiftUI, Firebase link to confirm Email - firebase

i would like to add the possibility to send a verification email during registration.
How could i insert "sendEmailVerificationWithCompletion" inside the function and within the SignupView? Thanks a lot :)
import SwiftUI
import FirebaseAuth
// Add code here:
static func createUser(withEmail email:String, name: String, password:String, completionHandler:#escaping (Result<Bool,Error>) -> Void) {
Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
if let err = error {
completionHandler(.failure(err))
return
}
guard let _ = authResult?.user else {
completionHandler(.failure(error!))
return
}
let data = FBUser.dataDict(uid: authResult!.user.uid, name: name, email: authResult!.user.email!)
FBFirestore.mergeFBUser(data, uid: authResult!.user.uid) { (result) in
completionHandler(result)
}
completionHandler(.success(true))
}
}
//And add code here:
struct SignUpView: View {
var body: some View {
VStack(spacing: 20 ) {
Button(action: {
FBAuth.createUser(withEmail: self.user.email, name: self.user.fullname, password: self.user.password) { (result) in
switch result {
case .failure(let error):
self.errorString = error.localizedDescription
self.showError = true
case .success(_):
print("Account creation successful")
}
}
}) {
Text("Register")
}
}
}
}

You can tell Firebase to send a verification email to the current user at any time after that user is signed in. For example, you could do so right after the user's account is created:
Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
if let err = error {
completionHandler(.failure(err))
return
}
guard let _ = authResult?.user else {
completionHandler(.failure(error!))
return
}
// send verification email
Auth.auth().currentUser?.sendEmailVerification { (error) in
// ...
}
// write profile to database
let data = FBUser.dataDict(uid: authResult!.user.uid, name: name, email: authResult!.user.email!)
FBFirestore.mergeFBUser(data, uid: authResult!.user.uid) { (result) in
completionHandler(result)
}
completionHandler(.success(true))
}

Related

missing emails in firebase auth for 20% of facebook credentials

I allow users to login with facebook on my app, backed by firebase authentication.
In around 20% of the facebook logins, I don't receive the user's email. I need the email address in my app, and can't figure out why I don't receive it.
Since I get the email address 80% of the time, I assume I have the right permissions setup to retrieve it.
I also enforced "One account per email address" in firebase-auth, so it seems to be a different issue than that raised in Firebase Auth missing email address.
Relevant extracts of my code:
export const FacebookSignUp: React.FC<SocialAuthProps & { title?: string }> = ({ onError, onSetWaiting, title }) => {
async function onFacebookButtonPress() {
onSetWaiting(true);
const { email, first_name, accessToken } = await getFacebookUserData();
const couldLogin = await tryLoginWithFacebook(email, accessToken);
if (!couldLogin) {
// Create a Firebase credential with the AccessToken
const facebookCredential = FacebookAuthProvider.credential(accessToken);
const userCredential = await firebaseAuth.signInWithCredential(facebookCredential);
if (userCredential.user === null) {
throw new Error("Null user");
}
const signupUser: SignupUserData = {
userId: userCredential.user.uid,
email,
pseudo: first_name || undefined
};
await createSignupUser(signupUser).then(() => {
onSetWaiting(false);
});
}
}
return (
<SocialButton
iconName="facebookIcon"
title={title || "S'inscrire avec Facebook"}
onPress={() =>
onFacebookButtonPress().catch((err) => {
onSetWaiting(false);
if (err instanceof SocialAuthError) {
onError(err);
} else if (err instanceof Error) {
const { message, name, stack } = err;
serverError("Unexpected signup error", { message, name, stack });
}
})
}
/>
);
};
import { LoginManager, AccessToken, GraphRequest, GraphRequestManager } from "react-native-fbsdk";
export async function getFacebookUserData(): Promise<FacebookInfo> {
LoginManager.logOut();
const result = await LoginManager.logInWithPermissions(["public_profile", "email"]);
if (result.isCancelled) {
throw "User cancelled the login process";
}
// Once signed in, get the users AccesToken
const { accessToken } = (await AccessToken.getCurrentAccessToken()) || {};
if (!accessToken) {
throw "Something went wrong obtaining access token";
}
return new Promise((resolve, reject) => {
let req = new GraphRequest(
"/me",
{
httpMethod: "GET",
version: "v2.5",
parameters: {
fields: {
string: "email,first_name"
}
}
},
(err, res) => {
if (err || res === undefined) {
reject(err);
} else {
const { first_name, email } = res as { first_name: string; email: string };
resolve({ first_name, email, accessToken });
}
}
);
new GraphRequestManager().addRequest(req).start();
});
}
Facebook allows you to opt out of passing your email along to third-party apps. You can request it, but the user can deny it.
If I ever log in with Facebook I always opt out of passing my email along - most of the time, the third-party app doesn't need it for legitimate purposes.
"I need the email address in my app" - why? email marketing? account duplication prevention?
In cases where you did not get an email, assume the user has opted-out and/or doesn't have an email tied to their account. If you need one, ask the user to input a contact email address and explain what you are using it for. Expect some users to still opt out and plan around it.
You could always convert their username into a non-existent email like theirusername#noreply.users.yourapp.com depending on your use case.

Cannot create document in Firestore when using Sign in with Apple

In my project I'm using a trigger to create a user document in Firestore when the user signs in. And this is great - everything works perfect for Google Sign-In and Facebook Login.
Here is this trigger:
exports.createAccountDocument = functions.auth.user().onCreate(async user => {
const { uid, displayName } = user
const username = displayName;
const email = user.email || user.providerData[0].email;
const profileImageUrl = uid;
const status = "active";
const str = username;
const qwery = [];
for (let i = 0; i < str.length; ++i) {
qwery[i] = str.substring(0, i + 1).replace(/\s/g, "").toLowerCase();
}
const keywords = qwery;
const bio = "";
return await admin
.firestore()
.collection("users")
.doc(uid)
.set({ bio, email, keywords, profileImageUrl, status, uid, username })
})
For example - this is Facebook Login method:
import SwiftUI
import FBSDKLoginKit
import Firebase
struct FacebookAuthView: UIViewRepresentable {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func makeCoordinator() -> FacebookAuthView.Coordinator {
return FacebookAuthView.Coordinator(showAnimation: self.$showAnimation, showSheet: self.$showSheet)
}
class Coordinator: NSObject, LoginButtonDelegate {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func loginButton(_ loginButton: FBLoginButton, didCompleteWith result: LoginManagerLoginResult?, error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
guard let token = AccessToken.current else {
return
}
self.showAnimation = true
self.showSheet = false
let credential = FacebookAuthProvider.credential(withAccessToken: token.tokenString)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error, (error as NSError).code == AuthErrorCode.credentialAlreadyInUse.rawValue {
Auth.auth().signIn(with: credential) { result, error in
// continue
print("signIn result: " + authResult!.user.email!)
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
} else {
// continue
print("Facebook Sign In")
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
}
}
func loginButtonDidLogOut(_ loginButton: FBLoginButton) {
try! Auth.auth().signOut()
}
}
func makeUIView(context: UIViewRepresentableContext<FacebookAuthView>) -> FBLoginButton {
let view = FBLoginButton()
view.permissions = ["email"]
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: FBLoginButton, context: UIViewRepresentableContext<FacebookAuthView>) { }
}
But when I try to create a document when user logs in using Swign in with Apple, this doesn't work. In the Firebase console under Firebase Authentication I can see new the user, but in Firestore, no document shows up at all.
Here is my Sign in with Apple method:
import Foundation
import SwiftUI
import AuthenticationServices
import CryptoKit
import Firebase
struct AppleAuthView: UIViewRepresentable {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
}
func makeCoordinator() -> AppleAuthView.Coordinator {
return AppleAuthView.Coordinator(showAnimation: self.$showAnimation, showSheet: self.$showSheet)
}
class Coordinator: NSObject, ASAuthorizationControllerPresentationContextProviding, ASAuthorizationControllerDelegate {
#Binding var showAnimation: Bool
#Binding var showSheet: Bool
fileprivate var currentNonce: String?
init(showAnimation: Binding<Bool>, showSheet: Binding<Bool>) {
self._showAnimation = showAnimation
self._showSheet = showSheet
super.init()
}
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
let viewController = UIApplication.shared.windows.last?.rootViewController
return (viewController?.view.window!)!
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print(error.localizedDescription)
return
}
print("Apple Sign In")
if let token = firebaseRegistrationPushToken {
checkUserAuthSettings(pushToken: token)
}
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
print("Sign in with Apple errored: \(error)")
}
#objc func startSignInWithAppleFlow() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.presentationContextProvider = self
authorizationController.performRequests()
}
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
#available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
}
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
let button = ASAuthorizationAppleIDButton(type: .signIn, style: .black)
button.addTarget(context.coordinator,action: #selector(Coordinator.startSignInWithAppleFlow),for: .touchUpInside)
return button
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
}
I don't understand why the document cannot be created. In the console I can see nil.
Please help to fix this issue.
Updated.
Code below - is a simple function to creating user document in firestore in my app
func signup(username: String, email: String, password: String, imageData: Data, completed: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
if !username.isEmpty && !email.isEmpty && !password.isEmpty && !imageData.isEmpty {
AuthService.signupUser(username: username, email: email, password: password, imageData: imageData, onSuccess: completed, onError: onError)
} else {
if username == "" {
errorString = "Please enter your name"
onError(errorString)
// showAlert = true
}
if email == "" {
errorString = "Please enter yor valid email"
onError(errorString)
// showAlert = true
}
if password == "" {
errorString = "Please create password"
onError(errorString)
// showAlert = true
}
if image == Image(IMAGE_USER_PLACEHOLDER) {
errorString = "Please upload your avatar"
onError(errorString)
// showAlert = true
}
}
}
and here is AuthService.signupUser method
static func signupUser(username: String, email: String, password: String, imageData: Data, onSuccess: #escaping(_ user: User) -> Void, onError: #escaping(_ errorMessage: String) -> Void) {
//Firebase.createAccount(username: username, email: email, password: password, imageData: imageData)
Auth.auth().createUser(withEmail: email, password: password) { (authData, error) in
if error != nil {
print(error!.localizedDescription)
onError(error!.localizedDescription)
return
}
guard let userId = authData?.user.uid else { return }
let storageAvatarUserId = Ref.STORAGE_AVATAR_USERID(userId: userId)
let metadata = StorageMetadata()
metadata.contentType = "image/jpg"
StorageService.saveAvatar(userId: userId, username: username, email: email, imageData: imageData, metadata: metadata, storageAvatarRef: storageAvatarUserId, onSuccess: onSuccess, onError: onError)
}
}
I know that apple sign in cannot take users image & it's ok - my cloud trigger fill this field in document users uid & in my app if user avatar = nil - it's takes universal clipart, It's ok, newer mind.
And this is User file
import Foundation
struct User: Encodable, Decodable {
var uid: String
var email: String
var profileImageUrl: String
var username: String
var bio: String
var keywords: [String]
var status: String?
}
my security rules in firebase are simple, here they are
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
This issue is - that Apple don’t get username.
Issue is solved.

Update isEmailVerified without logging out

I am trying to update the isEmailVerified property without logging out of the application. So far I've been able to only update it when I log out of the application.
Here is what I have so far:
struct ContentView: View {
#EnvironmentObject var session: SessionStore
var body: some View {
Group {
if (self.session.session != nil) {
let user = Auth.auth().currentUser
VStack{
Text("Logged In As \(user?.email ?? "")")
Text("\(String(self.session.session!.isEmailVerified!))")
Button(action: {user?.sendEmailVerification { (error) in
}}){
Text("Verify email")
}
Button(action: {session.signOut()}){
Text("Sign Out")
}
}
} else {
OnBoardingView()
}
}.onAppear(perform: {
session.listen()
})
}
}
and
struct User {
var uid: String
var email: String?
var isEmailVerified: Bool?
init(uid: String, displayName: String?, email: String?, isEmailVerified: Bool?) {
self.uid = uid
self.email = email
self.isEmailVerified = isEmailVerified
}
}
class SessionStore : ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
var isLoggedIn = false { didSet { self.didChange.send(self) }}
#Published var session: User? { didSet { self.didChange.send(self) }}
var handle: AuthStateDidChangeListenerHandle?
init(session: User? = nil) {
self.session = session
}
func listen () {
// monitor authentication changes using firebase
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// if we have a user, create a new user model
print("Got user: \(user)")
self.isLoggedIn = true
DispatchQueue.main.async {
self.session = User(
uid: user.uid,
displayName: user.displayName,
email: user.email,
isEmailVerified: user.isEmailVerified
)
}
} else {
// if we don't have a user, set our session to nil
self.isLoggedIn = false
self.session = nil
}
}
}
#discardableResult func signOut () -> Bool {
do {
try Auth.auth().signOut()
// self.isLoggedIn = false
// self.session = nil
return true
} catch {
return false
}
}
func signUp (
email: String,
password: String,
handler: #escaping AuthDataResultCallback
)
{Auth.auth().createUser(withEmail: email, password: password, completion: handler)}
func signIn (
email: String,
password: String,
handler: #escaping AuthDataResultCallback
)
{Auth.auth().signIn(withEmail: email, password: password, completion: handler)}
}
So far the two solutions I've gotten are that I need to refresh the user object using the following:
Auth.auth()?.currentUser.reload()
Or I can force it to get a new ID token using:
func getIDTokenResult(forcingRefresh forceRefresh: Bool, completion: ((FIRAuthTokenResult?, Error?) -> Void)? = nil)
However, since I'm new to coding I'm not sure I would implement this in my code. Any help would be appreciated. Thanks
I know this is quite an old discussion thread, but it seems no one has answered your question. If I am understanding your issue correctly, you can refresh the .isEmailVerified value (in Swift 4) like so:
Auth.auth().currentUser?.reload(completion: { (error) in
if let error = error {
// There was an error reloading the currentUser object
// Do something with the error here
} else {
// Success!
// Do anything you wanted to do with the refreshed .isEmailVerified in here!
print("The refreshed .isEmailVerified = \(Auth.auth().currentUser?.isEmailVerified)")
}
})

Sign up with email, first name, last name using SwiftUI and Firestore

I want to authenticate the user and create an user database with Firestore using users name ( first and last ). I can create the user but I couldn’t manage to create the database at the same time.
This is my SessionStore which works for signup and signin. By Signup I want to grab the name of the user and create a database. ( no code down below because nothing worked)
I am a beginner so I would appreciate an elaborate answer.
Thanks a lot!
import Firebase
import Combine
class SessionStore: ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
#Published var session: User? {didSet {self.didChange.send(self) }}
var handle: AuthStateDidChangeListenerHandle?
func listen() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
self.session = User(uid: user.uid, email: user.email)
} else {
self.session = nil
}
})
}
func signUp(email: String, password: String, handler: #escaping AuthDataResultCallback) {
Auth.auth().createUser(withEmail: email, password: password, completion: handler)
}
func signIn(email: String, password: String, handler: #escaping AuthDataResultCallback) {
Auth.auth().signIn(withEmail: email, password: password, completion: handler)
}
func signOut() {
do {
try Auth.auth().signOut()
self.session = nil
} catch {
print("Error signing out")
}
}
func unbind() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
deinit {
unbind()
}
}
struct User {
var uid: String
var email: String?
init(uid: String, email: String?) {
self.uid = uid
self.email = email
}
}
```
Firebase Authentication manages user authentication - while it does store additional information provided by some of the federated identity providers (such as profile picture URL), it isn't intended to be a profile management solution. If you'd like to store additional information about your users, you can implement this using Cloud Firestore or Firebase Realtime Database.
A Firebase user object only has a limited number of attributes, mostly to allow an authentication provider to authenticate the user. For Swift, the UserInfo defines the following:
providerId - the provider identifier
uid - the provider's unique ID for the user
displayName - the name of the user (i.e. their full name)
photoURL - the URL of the user's profile photo (if supported by the auth provider!)
email - the user's email address
phoneNumber - the user's phone number, only available if the user authenticated via phone number auth
So, what you need to do is:
Build your UI so it allows users to provide their credentials (email / password) as well as any additional profile info that you'd like to capture (such as first name, last name, place of residence)
Create the user using Auth.auth().createUser(withEmail: email, password: password)
If that call returns successfully, create a user profile in Cloud Firestore or Firebase Realtime Database
Here is a sample implementation.
Don't forget to:
Download the GoogleService-Info.plist for your project
Call FirebaseApp.configure() in your AppDelegate
Create a Cloud Firestore database in your project (using the Firebase Console)
User Interface
// File: ContentView.swift
import SwiftUI
struct ContentView: View {
#State var firstName: String = ""
#State var lastName: String = ""
#State var city: String = ""
#State var email: String = ""
#State var password: String = ""
#State var confirmPassword: String = ""
#State var showSignUpForm = true
#State var showDetails = false
#ObservedObject var sessionStore = SessionStore()
#State var profile: UserProfile?
var body: some View {
NavigationView {
VStack {
if self.showSignUpForm {
Form {
Section {
TextField("First name", text: $firstName)
.textContentType(.givenName)
TextField("Last name", text: $lastName)
.textContentType(.familyName)
TextField("City", text: $city)
.textContentType(.addressCity)
}
Section {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
SecureField("Confirm password", text: $confirmPassword)
}
Button(action: { self.signUp() }) {
Text("Sign up")
}
}
.navigationBarTitle("Sign up")
}
else {
Form {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
Button(action: { self.signIn() }) {
Text("Sign in")
}
}
.navigationBarTitle("Sign in")
}
Button(action: { self.showSignUpForm.toggle() }) {
Text(self.showSignUpForm ? "Have an account? Sign in instead." : "No account yet? Click here to sign up instead.")
}
}
.sheet(isPresented: $showDetails) {
UserProfileView(userProfile: self.profile ?? UserProfile(uid: "", firstName: "", lastName: "", city: ""))
}
}
}
func signUp() {
sessionStore.signUp(email: self.email, password: self.password, firstName: self.firstName, lastName: self.lastName, city: self.city) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
func signIn() {
sessionStore.signIn(email: self.email, password: self.password) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// File: UserProfileView.swift
import SwiftUI
struct UserProfileView: View {
var userProfile: UserProfile
var body: some View {
NavigationView {
Form {
Text(userProfile.uid)
Text(userProfile.firstName)
Text(userProfile.lastName)
Text(userProfile.city)
}
.navigationBarTitle("User \(userProfile.uid)")
}
}
}
struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
let userProfile = UserProfile(uid: "TEST1234", firstName: "Peter", lastName: "Friese", city: "Hamburg")
return UserProfileView(userProfile: userProfile)
}
}
Repositories
// File: SessionStore.swift
import Foundation
import Combine
import Firebase
class SessionStore: ObservableObject {
#Published var session: User?
#Published var profile: UserProfile?
private var profileRepository = UserProfileRepository()
func signUp(email: String, password: String, firstName: String, lastName: String, city: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing up: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed up.")
let userProfile = UserProfile(uid: user.uid, firstName: firstName, lastName: lastName, city: city)
self.profileRepository.createProfile(profile: userProfile) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signIn(email: String, password: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing in: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed in.")
self.profileRepository.fetchProfile(userId: user.uid) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signOut() {
do {
try Auth.auth().signOut()
self.session = nil
self.profile = nil
}
catch let signOutError as NSError {
print("Error signing out: \(signOutError)")
}
}
}
// File: UserProfileRepository.swift
import Foundation
import Firebase
import FirebaseFirestoreSwift
struct UserProfile: Codable {
var uid: String
var firstName: String
var lastName: String
var city: String
}
class UserProfileRepository: ObservableObject {
private var db = Firestore.firestore()
func createProfile(profile: UserProfile, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
do {
let _ = try db.collection("profiles").document(profile.uid).setData(from: profile)
completion(profile, nil)
}
catch let error {
print("Error writing city to Firestore: \(error)")
completion(nil, error)
}
}
func fetchProfile(userId: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
db.collection("profiles").document(userId).getDocument { (snapshot, error) in
let profile = try? snapshot?.data(as: UserProfile.self)
completion(profile, error)
}
}
}

SwiftUI using Firebase Authentication

I'm trying to use SwiftUI + Firebase Authentication via the email/password login. My question is, is there any way to append profile information to the user's authentication information when they create an account, or would I have to use Firebase Auth in tandem with Firestore or Firebase Database? I'm just trying to collect the user's first and last name and possibly city/state/country.
import SwiftUI
import Firebase
import Combine
class SessionStore: ObservableObject {
var didChange = PassthroughSubject<SessionStore, Never>()
#Published var session: User? {didSet {self.didChange.send(self) }}
var handle: AuthStateDidChangeListenerHandle?
func listen() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
self.session = User(uid: user.uid, email: user.email, displayName: user.displayName)
} else {
self.session = nil
}
})
}
func signUp(email: String, password: String, handler: #escaping AuthDataResultCallback) {
Auth.auth().createUser(withEmail: email, password: password, completion: handler)
}
func signIn(email: String, password: String, handler: #escaping AuthDataResultCallback) {
Auth.auth().signIn(withEmail: email, password: password, completion: handler)
}
func signOut() {
do {
try Auth.auth().signOut()
self.session = nil
} catch {
print("Error Signing Out")
}
}
func unbind() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
deinit {
unbind()
}
}
struct User {
var uid: String
var email: String?
var displayName: String?
init(uid: String, email: String?, displayName: String?) {
self.uid = uid
self.email = email
self.displayName = displayName
}
}
Firebase Authentication manages user authentication - while it does store additional information provided by some of the federated identity providers (such as profile picture URL), it isn't intended to be a profile management solution. If you'd like to store additional information about your users, you can implement this using Cloud Firestore or Firebase Realtime Database.
Please note that the Custom Claims feature is intended for managing advanced role management features - the documentation actually discourages developers from using this feature for storing additional user information, as custom claims are in fact stored in the ID token.
As #krjw correctly mentions, you don't need to use PassthroughObject when using #Published.
Here is a sample implementation:
// File: UserProfileRepository.swift
import Foundation
import Firebase
import FirebaseFirestoreSwift
struct UserProfile: Codable {
var uid: String
var firstName: String
var lastName: String
var city: String
}
class UserProfileRepository: ObservableObject {
private var db = Firestore.firestore()
func createProfile(profile: UserProfile, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
do {
let _ = try db.collection("profiles").document(profile.uid).setData(from: profile)
completion(profile, nil)
}
catch let error {
print("Error writing city to Firestore: \(error)")
completion(nil, error)
}
}
func fetchProfile(userId: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
db.collection("profiles").document(userId).getDocument { (snapshot, error) in
let profile = try? snapshot?.data(as: UserProfile.self)
completion(profile, error)
}
}
}
// File: SessionStore.swift
import Foundation
import Combine
import Firebase
class SessionStore: ObservableObject {
#Published var session: User?
#Published var profile: UserProfile?
private var profileRepository = UserProfileRepository()
func signUp(email: String, password: String, firstName: String, lastName: String, city: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().createUser(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing up: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed up.")
let userProfile = UserProfile(uid: user.uid, firstName: firstName, lastName: lastName, city: city)
self.profileRepository.createProfile(profile: userProfile) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signIn(email: String, password: String, completion: #escaping (_ profile: UserProfile?, _ error: Error?) -> Void) {
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if let error = error {
print("Error signing in: \(error)")
completion(nil, error)
return
}
guard let user = result?.user else { return }
print("User \(user.uid) signed in.")
self.profileRepository.fetchProfile(userId: user.uid) { (profile, error) in
if let error = error {
print("Error while fetching the user profile: \(error)")
completion(nil, error)
return
}
self.profile = profile
completion(profile, nil)
}
}
}
func signOut() {
do {
try Auth.auth().signOut()
self.session = nil
self.profile = nil
}
catch let signOutError as NSError {
print("Error signing out: \(signOutError)")
}
}
}
For the UI:
// File: ContentView.swift
import SwiftUI
struct ContentView: View {
#State var firstName: String = ""
#State var lastName: String = ""
#State var city: String = ""
#State var email: String = ""
#State var password: String = ""
#State var confirmPassword: String = ""
#State var showSignUpForm = true
#State var showDetails = false
#ObservedObject var sessionStore = SessionStore()
#State var profile: UserProfile?
var body: some View {
NavigationView {
VStack {
if self.showSignUpForm {
Form {
Section {
TextField("First name", text: $firstName)
.textContentType(.givenName)
TextField("Last name", text: $lastName)
.textContentType(.familyName)
TextField("City", text: $city)
.textContentType(.addressCity)
}
Section {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
SecureField("Confirm password", text: $confirmPassword)
}
Button(action: { self.signUp() }) {
Text("Sign up")
}
}
.navigationBarTitle("Sign up")
}
else {
Form {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocapitalization(.none)
SecureField("Password", text: $password)
Button(action: { self.signIn() }) {
Text("Sign in")
}
}
.navigationBarTitle("Sign in")
}
Button(action: { self.showSignUpForm.toggle() }) {
Text(self.showSignUpForm ? "Have an account? Sign in instead." : "No account yet? Click here to sign up instead.")
}
}
.sheet(isPresented: $showDetails) {
UserProfileView(userProfile: self.profile ?? UserProfile(uid: "", firstName: "", lastName: "", city: ""))
}
}
}
func signUp() {
sessionStore.signUp(email: self.email, password: self.password, firstName: self.firstName, lastName: self.lastName, city: self.city) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
func signIn() {
sessionStore.signIn(email: self.email, password: self.password) { (profile, error) in
if let error = error {
print("Error when signing up: \(error)")
return
}
self.profile = profile
self.showDetails.toggle()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// File: UserProfileView.swift
import SwiftUI
struct UserProfileView: View {
var userProfile: UserProfile
var body: some View {
NavigationView {
Form {
Text(userProfile.uid)
Text(userProfile.firstName)
Text(userProfile.lastName)
Text(userProfile.city)
}
.navigationBarTitle("User \(userProfile.uid)")
}
}
}
struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
let userProfile = UserProfile(uid: "TEST1234", firstName: "Peter", lastName: "Friese", city: "Hamburg")
return UserProfileView(userProfile: userProfile)
}
}
CombineFirebase library will simplify your problem.
You can have simple signIn/stateChangePublisher flatMapped with your userProfilePublisher

Resources