firebaseauthenticationfirebase-authenticationfirebase-dynamic-linkspassword-less

Can I skip the first step in the passwordless signin method in Firebase?


I have a list of people with all their personal information (name, first name, date of birth, email, etc.).I want to send to each of these people an email with a link allowing them, once clicked, to be directly connected on our website. Without having to type a password.


I followed the Firebase procedure for passwordless authentication:

But most of the examples don't quite fit my use case.

Most of the examples:

  1. User comes to your website, asks for passwordless authentication, types in his email, (the email is stored in window.location.href)
  2. User receives an email with a link to log in, he clicks on it
  3. User is on your website, logged in (thanks to his email stored in window.location.href).

My use case:

  1. None. I already have the email of my user, so I send him directly the link to connect.
  2. User receives an email with a link to log in, he clicks on it
  3. User is on my website, but has to type his e-mail again in the prompt (because it is obviously not stored in window.location.href).

In my case the window.location.href variable will never be used. And I don't want my user to have to retype his email once the link is clicked. Since I already have his email, why ask him again?


So how can I skip this step? Is there any security risk in doing so?


This is my code so far:

Back:

import firebase_admin
from firebase_admin import auth
from google.cloud import firestore


def create_new_auth(dictionary):
    user = auth.create_user(
        email=dictionary['email'],
        email_verified=True,
        phone_number=dictionary['phone'],
        password='super_secure_password_007',
        display_name=f"{dictionary['firstName']} {dictionary['lastName']}",
        disabled=False)
    print('Sucessfully created new user: {0}'.format(user.uid))

    return user.uid


def create_new_pre_user(db, dictionary, uid):
    dictionary = {
        'uid': uid,
        'email': dictionary['email'],
        'lastName': dictionary['lastName'],
        'gender': dictionary['gender'],
        'birthday': dictionary['birthday'],
        'phone': dictionary['phone'],
        'firstName': dictionary['firstName']
    }
    db.collection(u'users').document(uid).set(dictionary)


def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)

    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )

    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)


if __name__ == '__main__':
    dictionary = {
        "firstName": "Jone",
        "lastName": "Doe",
        "birthday": 12345678,
        "gender": "male",
        "email": "john.doe@gmail.com",
        "phone": "+33611223344"
    }
    main(dictionary)

Front:

private signInWithEmail() {
    if (this.authService.isSignInWithEmailLink(window.location.href)) {
      // Additional state parameters can also be passed via URL.
      // This can be used to continue the user's intended action before triggering
      // the sign-in operation.
      // Get the email if available. This should be available if the user completes
      // the flow on the same device where they started it.
      let email = window.localStorage.getItem('emailForSignIn');
      if (!email) {
        // User opened the link on a different device. To prevent session fixation
        // attacks, ask the user to provide the associated email again. For example:
        email = window.prompt('Please provide your email for confirmation');
      }
      // The client SDK will parse the code from the link for you.
      this.authService.signInWithEmailLink(email, window.location.href)
        .then((result) => {
          // Clear email from storage.
          window.localStorage.removeItem('emailForSignIn');
          // You can access the new user via result.user
          // Additional user info profile not available via:
          // result.additionalUserInfo.profile == null
          // You can check if the user is new or existing:
          // result.additionalUserInfo.isNewUser
          this.router.navigate(['/patient', 'quiz'])
        })
        .catch((error) => {
          // Some error occurred, you can inspect the code: error.code
          // Common errors could be invalid email and invalid or expired OTPs.
        });
    }
  }
isSignInWithEmailLink(href) {
    return this.afAuth.auth.isSignInWithEmailLink(href);
  }

  signInWithEmailLink(email: string, href: string) {
    return this.afAuth.auth.signInWithEmailLink(email, href)
  }

EDITS


The problem is that the front has no knowledge of the user email when he first come the our website using the link. There is a way to pass the email information from our server-side to the front but it's in clear in the URL : that's risky and not a good practice according to Firebase itself (link)

Like this:

def main(dictionary):
    firebase_admin.initialize_app()
    db = firestore.Client()
    uid = create_new_auth(dictionary)
    create_new_pre_user(db, dictionary, uid)

    action_code_settings = auth.ActionCodeSettings(
        url=f'http://localhost:4200/login/?email=john.doe@gmail.com',
        handle_code_in_app=True,
        ios_bundle_id='com.example.ios',
        android_package_name='com.example.android',
        android_install_app=True,
        android_minimum_version='12',
        dynamic_link_domain='magic42.page.link',
    )

    link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)

So how can I pass the email information from the back to the front so that the user doesn't have to type it again when redirected to my website after clicking to my "magic link" ?


Solution

  • One thing you could do is to create a single-use token on the backend that links to your user's email (or that links to a document in firestore) and have that be in the url. When the user enters the page, make a call to your backend with the token (could be just a simple uuid) and have your backend sign the user in and then expire/remove that token from use.

    E.G.

    https://yoursite.com/44ed3716-2b8f-4068-a445-b05a8fee17c3
    

    Frontend sends 44ed3716-2b8f-4068-a445-b05a8fee17c3 to backend...backend sees the token, logs them in, then makes that token no longer valid.

    Update

    To answer your question in the comments below about not needing email link auth anymore through firebase: not necessarily. At that point, you're kind of creating your own email sign-in system (which actually isn't too hard) and somewhat re-inventing the wheel. Adding a token to the url was just a way for you to associate the user with an email without having to actually put the email in the url so that your frontend can know who the user is once your link is clicked. Once the backend sends you the email, you can store it local storage and complete the sign in with firebase normally.