Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Cloud Function should support user.sendEmailVerification() like client sdk #46

Open
inlined opened this issue Jun 22, 2017 · 68 comments

Comments

@inlined
Copy link
Member

inlined commented Jun 22, 2017

[Refiling for user @Sun3 from https://github.com/firebase/functions-samples/issues/181]

Feature Request:

The user.sendEmailVerification() needs to be supported by Firebase Cloud Functions. This is available in client side but not server side. When using Cloud Functions to create new users we also need to automatically send Email Verification before new user can use our apps. This is currently as stopping block for our apps.

At this point the user.sendEmailVerification() gives error that function is not found.

    admin.auth().createUser({
        email: emailAddress,
        emailVerified: false,
        password: password,
        displayName: '', //name,
        disabled: false
    })
        .then(function (user) {
            // A error representation of the newly created user is returned
            console.log("Created Firebase User successfully with id: ", user.uid);
            console.log("user.emailVerified:", user.emailVerified);

            // Send Email Verification
            user.sendEmailVerification()
                .then(function (emailSent) {
                    console.log('emailSent ', emailSent);
                })
                .catch(function (error) {
                    console.log('emailSent error ', error);
                });

    // ... Additional code below

Thank you and I am open to any suggestions.

@google-oss-bot
Copy link

Hey there! I couldn't figure out what this issue is about, so I've labeled it for a human to triage. Hang tight.

@google-oss-bot
Copy link

Hmmm this issue does not seem to follow the issue template. Make sure you provide all the required information.

@Sun3
Copy link

Sun3 commented Jun 22, 2017

The issue/new feature request is that the user.sendEmailVerification() function is not available on the firebase admin (firebase cloud functions), but it is available in the client sdk like the one used in angular 4.

From the Documentation when you create a New user. I added the sendEmailVerification() which this function is not available. But it's a must to allow at this time for the new user to verify their email before we allow access to our apps.

admin.auth().createUser({
  email: "user@example.com",
  emailVerified: false,
  password: "secretPassword",
  displayName: "John Doe",
  photoURL: "http://www.example.com/12345678/photo.png",
  disabled: false
})
  .then(function(userRecord) {
    // See the UserRecord reference doc for the contents of userRecord.
    console.log("Successfully created new user:", userRecord.uid);
    
    // ****************************************************************
    // Cannot request/call the sendEmailVerification() function, not available
    // Send Email Verification
            user.sendEmailVerification()
                .then(function (emailSent) {
                    console.log('emailSent ', emailSent);
                })
                .catch(function (error) {
                    console.log('emailSent error ', error);
                });
    // End send email verification not available
    // ****************************************************************


  })
  .catch(function(error) {
    console.log("Error creating new user:", error);
  });

Please let me know if this explains the issue.
Thanks.

@bojeil-google bojeil-google self-assigned this Jun 23, 2017
@rkpradeep20
Copy link

Yes, would like this feature in cloud functions. user.sendEmailVerification()

@coreybutler
Copy link

The docs make it seem like its possible to verify a new user's email address with the firebase email validation using a cloud function. Lack of this feature forces developers to use an external service (which requires an upgraded paid plan). Requiring a third party dependency for something Firebase already does well is clunky for developers and confusing.

@bojeil-google
Copy link
Contributor

Hey @coreybutler, the email verification Firebase Auth sends is not a welcome nor a goodbye email (the link you pointed it out). Nor should it be used for that.

You also have the option to send an email verification client side for free. So if you want to send an email verification, you can always do it for free.

Let's keep these issues separate (sending email verifications, vs sending other personalized emails for non-auth specific reasons). We acknowledge the value of sending an email verification, server side via the admin SDK, and we are looking into it.

@coreybutler
Copy link

@bojeil-google - thanks.

Purely for context, my "app" doesn't have a front end. In my case, I'm using a cloud function to serve as a Docker Authentication Proxy... 100% server-side. Users are added directly through the Firebase console.

If this is a separate issue (which is fine), the docs should clarify. Perhaps indicate what limitations exist. A document titled "Extend Firebase Authentication with Cloud Functions" and a subtitle of "Trigger a function on user creation" seems like it would encompass email verification.

@bojeil-google
Copy link
Contributor

Hey @coreybutler, to help unblock you, you can always use the client SDK to send the email verification from a Firebase Function. You'd need to require firebase module.
On user creation, you do the following:

  1. get the uid of the user created from the onCreate trigger.
  2. Using admin SDK, mint custom token via createCustomToken for that user.
  3. Using client SDK, signInWithCustomToken using that custom token.
  4. Send email verification via currentUser.sendEmailVerification.

@PoiScript
Copy link

+1, for now, I'm sending verification email by hitting the restful api. What a mess..

@inlined
Copy link
Member Author

inlined commented Oct 13, 2017

BTW, I'm trying to catch examples of sub-optimal Promise usage in the wild and wanted to point out the one in this thread, CC @Sun3 Promise code should almost never repeatedly nest. These aren't callbacks anymore:

    admin.auth().createUser({
        email: emailAddress,
        emailVerified: false,
        password: password,
        displayName: '', //name,
        disabled: false
    }).then(function (user) {
        // A error representation of the newly created user is returned
        console.log("Created Firebase User successfully with id: ", user.uid);
        console.log("user.emailVerified:", user.emailVerified);

        // Send Email Verification
       return user.sendEmailVerification();
    }).then(function (emailSent) {
       console.log('emailSent ', emailSent);

    }).catch(function (error) {
       console.log('emailSent error ', error);
    });
    // ... Additional code below

This code should hopefully be more readable. It also fixes two bugs:

  1. user.sendEmailVerificaiton wasn't being returned, so the outer promise was resolving early to undefined
  2. Because the catch block was on the outer promise, there was no handler for a failure to create the new user (may have been intentional from the error message, but you should always handle rejection.

@sweetcoco
Copy link

sweetcoco commented Oct 14, 2017

@bojeil-google While that would work to unblock developers who need this urgently, its an incredibly elaborate workaround once you figure in the need for service account certs for minting custom tokens... in my case where i have multiple environments where i would need to manage certs based on environment, its just a ton of mental overhead. It would be nice if cloud functions didn't need service account certs anyway.

Any word if this feature is being worked on?

@bojeil-google
Copy link
Contributor

The feature is on our list and we acknowledge its importance. We just have a lot of feature requests and many are currently critical to developers and not possible client side or admin side. I would prioritize those higher.

If you are having a hard time minting custom tokens, you can just send the ID token to an HTTP endpoint you host in Firebase Functions and use the REST API to send the email verifications:
https://firebase.google.com/docs/reference/rest/auth/#section-send-email-verification

@sweetcoco
Copy link

@bojeil-google Thanks a ton!

@coreybutler
Copy link

@bojeil-google - sorry for the delayed response. Are you suggesting the firebase client SDK can be used from Node (i.e. within the function)? Just to reiterate, I have no front end and no browser, so all logic must be encapsulated within the Firebase function (which is what I was trying to do). Step 3 is what I'm questioning the viability of. I thought the client SDK relied on browser-specific capabilities, such as XHR, which would make this impossible. I'd love to be wrong about that ;-)

The goal of my project is for users to login to Docker via a terminal/shell, using Firebase auth. The Docker agent handles all of the handshaking, but uses HTTP requests to authenticate users. Basically, Docker attempts to login via basic auth first, so my function extracts the credentials and matches them up with a Firebase user. That was pretty simple.

I wanted my code to assure the user had a verified email address. We know and trust users, but we don't always trust someone to type in the right email... typos are rampant. So, the goal was to setup a function that would send the verification email whenever a new user was created, regardless of how they're created (which is exclusively through the web console at the moment).

I know this isn't a traditional use of Firebase, and our goal is to eventually have a more traditional UI/self-registration portal, which would make all of this moot. However; we don't have enough time for a project of that scale right now. Personally, I'd be even happier if there were an option in the Firebase console to automatically send new users a verification email so we don't have to write this functionality ourselves... but I'm also perfectly content doing it myself if it's possible.

@inlined
Copy link
Member Author

inlined commented Oct 23, 2017

Yes, the client SDK works in Node. We actually use the Client SDK in Node as part of firebase-tools, so this is pretty unlikely to change any time soon.

@rwmb
Copy link

rwmb commented Apr 3, 2018

I have the latest version of the admin sdk but I don't see this feature implemented. Do we still have to login as the user in order to send the verification?
If so, I couldn't find a solution or reason for this issue to be closed.
Thanks a lot.

@hiranya911
Copy link
Contributor

This issue is not closed.

@rwmb
Copy link

rwmb commented Apr 3, 2018

Oh ok, my bad. I saw the reference and mistaken it for an action in this issue.
Thank you @hiranya911
So +1 for this feature 👍

@lingxiao
Copy link

Any updates on this issue? It seems like a pretty crucial feature...

@cdiaz
Copy link

cdiaz commented Jun 17, 2018

I'm still waiting it this feature to be available soon

@fcarreno
Copy link

fcarreno commented Jul 29, 2018

Agree this will be a great feature to add to the Admin SDK.

I understand an ID token would really not be required to perform these type of operation from the server side and assume the plan is to just use service account credentials, just like with other sever side/admin APIs.

That said, with the current situation, I gave it a shot with a combination of sign up + send email verification REST APIs:

https://firebase.google.com/docs/reference/rest/auth/#section-create-email-password
(returns an ID token if successful)

https://firebase.google.com/docs/reference/rest/auth/#section-send-email-verification

And that worked well.
Not an ideal flow, and possibly simpler to just use a 3rd party transactional/cloud email provider - but looks promising as an interim solution/workaround.

Did not check options available to customize the email using this approach (if related at all), neither any possible throttling issues involved, given this will be always triggered from the same (server) IP address, though....(not sure if the same rules than using the client SDK would apply for example)

In addition, the ID token will really be discarded in this case, since it's solely used for the purpose of getting the email sent. Expecting to be able to get a new one from the client, once the email is verified.

@Rekodr
Copy link

Rekodr commented Aug 1, 2018

Is this feature already available ? ; this is really needed.

@thebrianbug
Copy link

thebrianbug commented Jan 15, 2020

Using the REST API is more of a workaround due to the SDK still lacking this functionality. In my opinion, ideally, the admin SDK should have a function to sendEmailVerification() for the current user. It is much more reliable to trigger this functionality on the OnCreate event of the user rather than relying on the client to handle this.

@Jaimeloeuf
Copy link

Been almost 3 years.....

@dungahk
Copy link

dungahk commented May 5, 2020

I do not think this will ever be implemented, they added other functionalities such as https://firebase.google.com/docs/auth/admin/email-action-links that pretty much covers 90% of this and the way I see it, that is the best solution going forward.

Yes, you would still need to use your own email service or a third-party one, but nowadays you can get it for free (SendGrid, for example).

@mariotacke
Copy link

Just came across this issue :/. Since this is already working for the client SDK, what is the suggested work-around for now without relying on a 3rd party service?

@coreybutler
Copy link

I'll just go ahead an ask.... is there actually any community interest in a 3rd party service to do this? Since my original posts, I've resolved this a number of different times. It's not rocket science from a code perspective, it's just a PITA to setup every time.

I was thinking along the lines of configuring a simple onCreate function to send a web request off to a service when a user is created.... service does everything else and could conceivably send a webhook back to systems once the verification is complete.

I don't want to hijack this thread, so ping me separately if anyone is interested in something like this.

@mariotacke
Copy link

mariotacke commented May 7, 2020

I found a work-around that works well enough for my use case, see below. I'm not sure if this is best practice, but I wanted to keep the emails exactly the same between the server and client requests. Would love to hear about any flaws with this implementation 💡

As suggested above, it uses a three step process to do this:

  1. Acquire a custom token via the admin sdk's createCustomToken(uid)
  2. It converts this custom token to an idToken via the API
  3. It invokes the send email verification endpoint on the API
const functions = require('firebase-functions');
const fetch = require('node-fetch');
const admin = require('firebase-admin');

const apikey = functions.config().project.apikey;
const exchangeCustomTokenEndpoint = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apikey}`;
const sendEmailVerificationEndpoint = `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apikey}`;

module.exports = functions.auth.user().onCreate(async (user) => {
  if (!user.emailVerified) {
    try {
      const customToken = await admin.auth().createCustomToken(user.uid);

      const { idToken } = await fetch(exchangeCustomTokenEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          token: customToken,
          returnSecureToken: true,
        }),
      }).then((res) => res.json());

      const response = await fetch(sendEmailVerificationEndpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          requestType: 'VERIFY_EMAIL',
          idToken: idToken,
        }),
      }).then((res) => res.json());

      // eslint-disable-next-line no-console
      console.log(`Sent email verification to ${response.email}`);
    } catch (error) {
      // eslint-disable-next-line no-console
      console.log(error);
    }
  }
});

References

@dmvvilela
Copy link

It's 2020.. nothing on this yet?? C'mon firebase team! I lost hours trying this because it was intuitive to make it server side and even on client side is not working.

@quantuminformation
Copy link

I'm pretty stumped why the admin would have less functionality than the insecure front end

@digimbyte
Copy link

I find the lack of user verification and email/password reset from the admin sdk to be a flawed and lacking feature

@Ranguna
Copy link

Ranguna commented Sep 27, 2020

+1 for this.
I'm currently using a combination of calling the identity toolkit login with password + send Oob code endpoints to do this, which just feels like a big workaround.

@Sun3
Copy link

Sun3 commented Oct 29, 2020

Firebase team, can you please shed light on this issue? The Admin SDK lack of using the sendEmailVerification() link is a serious issue not being address. It should not rely on third party to actually send the email out, it's a standard Firebase auth fuction.

I first reported this issue back Jun 22, 2017.

Thank you.

@stehag
Copy link

stehag commented Feb 6, 2021

Not having this feature after so many years makes one wonder what is happening, the developer experience is really bad, of course this should have been implemented at the same time as it was possible to create users with the Admin SDK.

@Ranguna
Copy link

Ranguna commented Feb 11, 2021

@stehag I'm not sure we should say that the developer experience is "really bad" just because of a few missing features here and there. But yeah, this one is a real must for a lot of things.

@inlined
Copy link
Member Author

inlined commented Feb 11, 2021

I've re-escalated this internally. Has anyone who has been waiting all this time tried just using the client SDK from the server side? I would expect that you could create a custom token in the admin SDK, pass it to the client SDK, and then call the appropriate method. I get that this is a kludge, but it's better than waiting on a complete backend API rewrite to support this edge case (sadly that's what would be required).

@myspivey
Copy link

I can confirm that works as it is what I did for the project I was on. It was very "hacky" and did not look good and only worked for custom auth flows not SSO. This was for a project where admins had to create users as self signup was not allowed. Once they had their custom signon, at that point they could switch to SSO.

@dungahk
Copy link

dungahk commented Feb 12, 2021

A few years ago I did exactly that, I guess that still works but I can confirm it used to work around 3 years ago. (Using the client SDK)

@Ranguna
Copy link

Ranguna commented Feb 14, 2021

@inlined

Has anyone who has been waiting all this time tried just using the client SDK from the server side?

Isn't the client sdk rate limited for client use ?

@inlined
Copy link
Member Author

inlined commented Feb 15, 2021

Rate limiting shouldn't be a problem AFAICT.

@Ranguna
Copy link

Ranguna commented Feb 15, 2021

So you are saying that a public facing lib is not rate limited by IP ?
Doesn't sound very realistic, I'd search the docs but they usually aren't very explicit about these things.

@inlined
Copy link
Member Author

inlined commented Feb 16, 2021

I see a ratelimit error in their service definition, but don't know (and probably shouldn't say) what the limit is. Generally even IP ratelimts are set to "reasonable" levels because ISPs in some countries NAT massive numbers of clients to the same IPv4. Either way, the rate limit is guaranteed to be greater than 0, so this is better than waiting for an architecture rewrite.

@Ranguna
Copy link

Ranguna commented Feb 17, 2021

@inlined , in that case, this should be a better solution #46 (comment), since it shouldn't be rate limited the same way a public facing lib is.
Unless they are using the same underlying endpoints.

@crossan007
Copy link

So, I have #46 (comment) working in local emulators when I manually specify the REST API key (const apikey = functions.config().project.apikey;).

Is there a way in a production environment to get the REST API key? or will I need to manually add that to the functions config via firebase functions:config:set?

@inlined
Copy link
Member Author

inlined commented Apr 12, 2021

The Firebase Config does not include an API key since the Firebase Config is for backend development and the API key is ostensibly to label a client. You can inject it with functions:config:set though.

@MrVentzi
Copy link

MrVentzi commented Aug 2, 2021

+1, we want to see this!

@Migaelh
Copy link

Migaelh commented Sep 2, 2021

I found a method on the Firebase Auth documentation where you can generate a email verification link, email password link or email link for sign-in.

You can also pass a redirect URL (and other info) where you can handle any callbacks once the use clicks on the link.

You just need to send the email yourself.

This is what I have done:

let displayName = 'John Doe'
let email = 'to@mail.com'
//Generate the email verification link
let emailVerificationLink = await admin.auth().generateEmailVerificationLink(email, { url: `SOME_REDIRECT_URL?with=params` })
let mail = JSON.parse(process.env.FIREBASE_CONFIG).mail

//construct the email 
const mailTransport = nodemailer.createTransport({
    service: 'gmail',
    auth: {
      user: mail.email,
      pass: mail.password,
    },
  })

const mailOptions = {
    from: `"${APP_NAME}" <${mail.email}>`,
    to: email,
    subject: `Email verification for ${APP_NAME}`,
    text: `Hello ${displayName})}

        Please follow this link to verify your email address for the ${APP_NAME}
        ${emailVerificationLink}

        Thanks
        Your ${APP_NAME} team
    `,
    html: `
        <p>Hello ${displayName}</p>
        <p>Please follow this link to verify your email address for the ${APP_NAME}</p>
        <p><a href='${emailVerificationLink}'>Verify Email</a></p>
        <p>Thanks</p>
        <p>Your ${APP_NAME} team</p>
    `
  };

  try {
  //send the email
    await mailTransport.sendMail(mailOptions);
  } catch(error) {
    console.error('send email error', error) 
  }

I am using the NodeMailer package for sending the email

@samasthwafer
Copy link

7 years later and no feature still?

@digimbyte
Copy link

Possible solution is to install the firebase client and initiate the auth with a custom token from the admin sdk and then invoke an email request on that user instance

@MadhavKanna
Copy link

I really do need this, guess I'll just have to implement the workaround. Any updates on when the solution should come out?

@digimbyte
Copy link

digimbyte commented Jul 14, 2024

I really do need this, guess I'll just have to implement the workaround. Any updates on when the solution should come out?

something like this can be done:
(doesn't need the client sdk)

const admin = require('firebase-admin');
const axios = require('axios');

admin.initializeApp({
  credential: admin.credential.applicationDefault(),
});

const generateCustomToken = async (uid) => {
  try {
    const customToken = await admin.auth().createCustomToken(uid);
    console.log('Custom Token:', customToken);
    return customToken;
  } catch (error) {
    console.error('Error creating custom token:', error);
  }
};

const authenticateWithCustomToken = async (customToken) => {
  const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${process.env.FIREBASE_API_KEY}`;
  try {
    const response = await axios.post(url, {
      token: customToken,
      returnSecureToken: true,
    });
    console.log('ID Token:', response.data.idToken);
    return response.data.idToken;
  } catch (error) {
    console.error('Error authenticating with custom token:', error);
  }
};

const sendVerificationEmail = async (idToken) => {
  const url = `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${process.env.FIREBASE_API_KEY}`;
  try {
    const response = await axios.post(url, {
      requestType: 'VERIFY_EMAIL',
      idToken: idToken,
    });
    console.log('Verification email sent:', response.data);
  } catch (error) {
    console.error('Error sending verification email:', error);
  }
};

// Replace with your user's UID
const uid = 'your-user-uid';

generateCustomToken(uid)
  .then(customToken => authenticateWithCustomToken(customToken))
  .then(idToken => sendVerificationEmail(idToken));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests