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 · 34 comments

Comments

Projects
None yet
@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.

@firebase-oss-bot

This comment has been minimized.

Copy link

firebase-oss-bot commented Jun 22, 2017

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

@firebase-oss-bot

This comment has been minimized.

Copy link

firebase-oss-bot commented Jun 22, 2017

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

@Sun3

This comment has been minimized.

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

This comment has been minimized.

Copy link

rkpradeep20 commented Jul 16, 2017

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

@coreybutler

This comment has been minimized.

Copy link

coreybutler commented Oct 9, 2017

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

This comment has been minimized.

Copy link
Contributor

bojeil-google commented Oct 9, 2017

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

This comment has been minimized.

Copy link

coreybutler commented Oct 9, 2017

@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

This comment has been minimized.

Copy link
Contributor

bojeil-google commented Oct 10, 2017

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

This comment has been minimized.

Copy link

PoiScript commented Oct 10, 2017

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

@inlined

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Contributor

bojeil-google commented Oct 16, 2017

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

This comment has been minimized.

Copy link

sweetcoco commented Oct 16, 2017

@bojeil-google Thanks a ton!

@coreybutler

This comment has been minimized.

Copy link

coreybutler commented Oct 16, 2017

@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

This comment has been minimized.

Copy link
Member

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

This comment has been minimized.

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

This comment has been minimized.

Copy link
Member

hiranya911 commented Apr 3, 2018

This issue is not closed.

@rwmb

This comment has been minimized.

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

This comment has been minimized.

Copy link

lingxiao commented May 29, 2018

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

@cdiaz

This comment has been minimized.

Copy link

cdiaz commented Jun 17, 2018

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

@fcarreno

This comment has been minimized.

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

This comment has been minimized.

Copy link

Rekodr commented Aug 1, 2018

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

@j-low

This comment has been minimized.

Copy link

j-low commented Aug 19, 2018

Formally seconding (thirding? fourthing?) this feature. Also, a bit more clarity in the docs that you cannot issue a verification email from Cloud Functions would be helpful.

Currently the Auth API for the client and Cloud Functions appear very similar (e.g. firebase.auth().currentUser... vs functions.auth.user()...) and it took a lot of digging and testing to figure out that I could call sendEmailVerification() from the former but not the latter. The flow has the appearance of convenience but it really felt like a head-fake when I learned that sending an email verification in a logical create-user flow (i.e. within the onCreate callback) could not actually be handled nearly as simply as the client-side feature seemed to imply.

@kkukshtel

This comment has been minimized.

Copy link

kkukshtel commented Aug 20, 2018

Fifthing (?) this as well. Would love to have this ability be a single call vs. needing to leverage the REST API.

@kkukshtel

This comment has been minimized.

Copy link

kkukshtel commented Aug 21, 2018

Okay got this working and wanted to post here so others have a reference implementation but also to really show the need for having this wrapped up in a simple function. A crazy thing I realized too was the difference between Auth customTokens and IdTokens, and that the Admin Auth SDK is only able to generate the former. This means you not only need to use the REST SDK to send an email, but also need to add in an additional call to exchange your customToken for an idToken. The Admin Auth SDK also has no method for verifying a customToken to generate an idToken (which is what it looks like the REST SDK does).

This uses the Admin SDK + REST SDK (and the request.js npm module) to send a verification email from the server when a new user signs up and all you have at that point is their UID:

auth.createCustomToken(userRecord.uid).then(function(customToken) {
    var idTokenRequestOps = 
    {
        url:'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=[APIKEY]',
        json: true,
        body: { "token" : customToken, "returnSecureToken" : true}
    };
    request.post(idTokenRequestOps, function optionalCallback(err, httpResponse, body) {
        if (err) {
            return console.error('unable to post request to send swap custom token for id token', err);
        }
        else {
            if(body.error) {
                console.log("unable to swap custom token for idtoken with error", body.error);
            }
            else {
                var emailVerificationOps = 
                {
                    url:'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key=[APIKEY]',
                    json: true,
                    body: { "requestType" : "VERIFY_EMAIL", "idToken" : body.idToken}
                };
                request.post(emailVerificationOps, function optionalCallback(err, httpResponse, body) {
                    if (err) {
                        return console.error('unable to post request to send verification email', err);
                    }
                    else {
                        if(body.error) {
                            console.log("unable to send email verification with error",body.error);
                        }
                        else {
                            console.log("sent verification email! server responded with",body);
                        }
                    }
                });
            }
        }
    });
});
@justdan0227

This comment has been minimized.

Copy link

justdan0227 commented Sep 24, 2018

So I'm going to ask. Has this gotten any better. Just a simple create a user and send the email verification link.

@ananthu-confianz

This comment has been minimized.

Copy link

ananthu-confianz commented Sep 27, 2018

Thank you @kkukshtel Its works like a charm!
But in order to work with createCustomToken() function we have to nitialize the admin with a service account like follow.
Refer: https://stackoverflow.com/questions/42717540/firebase-cloud-functions-createcustomtoken

var serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://< Project ID>.firebaseio.com"
});```

Here is my complete code:

```const cloudStorageConfig = require('./cloud-storage-config.json');
const admin = require('firebase-admin');
var serviceAccount = require('./serviceAccountKey.json');
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://catalog-e8e80.firebaseio.com"
});
const request = require('request');

exports.customerSignUp = functions.https.onRequest((req, res) => {
  cors(req, res, () => { });
admin.auth().createUser({
      email: req.body.email,
      password: req.body.password
    }).then(function (userRecord) {
      sendVerificationEmail(userRecord).then(()=>{
res.status(200).send({ "success": true, uid: userRecord.uid });      
).catch((error)=>{
res.status(200).send({ "success": false, error: error });
})
    })
});

function sendVerificationEmail(userRecord) {
  return admin.auth().createCustomToken(userRecord.uid).then(function (customToken) {
    var header = {
      'Content-Type': 'application/json'
    }
    return request({
      url: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyCustomToken?key=" + cloudStorageConfig.apiKey,
      method: 'POST',
      json: { "token": customToken, "returnSecureToken": true },
      headers: header
    },
      function (error, response, body) {
        if (error) {
          console.log('unable to post request to send swap custom token for id token', error);
          return;
        }
        if (body.error) {
          console.log('unable to swap custom token for idtoken with error', body.error);
          return;
        } else {
          return request({
            url: "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode?key=" + cloudStorageConfig.apiKey,
            method: 'POST',
            json: { "requestType": "VERIFY_EMAIL", "idToken": body.idToken },
            headers: header
          },
            function (err, response, body) {
              if (err) {
                console.error('unable to post request to send verification email', err);
              } else {
                if (body.error) {
                  console.log("unable to send email verification with error", body.error);
                } else {
                  console.log("sent verification email! server responded with", body);
                }
              }
              return;
            }
          );
        }
      }
    );
  });
}```
@wesleyfuchter

This comment has been minimized.

Copy link

wesleyfuchter commented Oct 3, 2018

Hi everyone!

How long this feature will be available?

Appreciate the attention.

@yuliankarapetkov

This comment has been minimized.

Copy link

yuliankarapetkov commented Nov 6, 2018

Hey, any update on this?

@justdan0227

This comment has been minimized.

Copy link

justdan0227 commented Nov 6, 2018

I'm having to call a nodejs module with a rest api in order to do it unless this has been now included

@rhass99

This comment has been minimized.

Copy link

rhass99 commented Nov 20, 2018

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.

@bojeil-google
How would I initialize the firebase app on the cloud functions?
firebase.initializeApp({config}) doesnt work, also firebase.initializeApp() doesnt work

Edit:
admin.intializeApp() works without passing config,
firebase.initializeApp({config}) Config is requires.
Now it works
Thanks

@LoneKlingon

This comment has been minimized.

Copy link

LoneKlingon commented Dec 16, 2018

I'd also like to request this feature at some point as well. While it probably isn't the most important feature, the alternative presented, creating a custom token and then posting to googleapis, isn't as simple as it could be. I am having issues with the post request accepting my custom id token and as far as I can tell I followed all of the documentation/steps. I can think of a client side work around but it would be easier to do this all server side.

Edit: I did eventually get it to work on the server but the point stands just adding a sendEmailVerification function would be the most straightforward method.

@rhass99

This comment has been minimized.

Copy link

rhass99 commented Dec 17, 2018

This is how I implemented it successfully following @bojeil-google earlier comment

Firebase Cloud function (background) triggered with every new user created

  • This function sends a "user" object to your api endpoint
const functions = require('firebase-functions');
const fetch = require('node-fetch');

// Send email verification through express server
exports.sendVerificationEmail = functions.auth.user().onCreate((user) => {
// Example of API ENPOINT URL 'https://mybackendapi.com/api/verifyemail/'
  return fetch(<API ENDPOINT URL>, {
    method: 'POST',
    body: JSON.stringify({ user: user} ),
    headers: {
      "Content-Type": "application/json"
    }
  }).then(res => console.log(res))
    .catch(err => console.log(err));
});

Server Middleware code

  • verifyEmail here is used as middleware
// File name 'middleware.js'
import firebase from 'firebase';
import admin from 'firebase-admin';

// Get Service account file from firebase console
// Store it locally - make sure not to commit it to GIT
const serviceAccount = require('<PATH TO serviceAccount.json FILE>');
// Get if from Firebase console and either use environment variables or copy and paste them directly
// review security issues for the second approach
const config = {
  apiKey: process.env.APIKEY,
  authDomain: process.env.AUTHDOMAIN,
  projectId: process.env.PROJECT_ID,
};
// Initialize Firebase Admin
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
});

// Initialize firebase Client
firebase.initializeApp(config);

export const verifyEmail = async (req, res, next) => {
  const sentUser = req.body.user;
  try {
    const customToken = await admin.auth().createCustomToken(sentUser.uid);
    await firebase.auth().signInWithCustomToken(customToken);
    const mycurrentUser = firebase.auth().currentUser;
    await mycurrentUser.sendEmailVerification();
    res.locals.data = mycurrentUser;
    next();
  } catch (err) {
    next(err);
  }
};

Server code

// Filename 'app.js'
import express from 'express';
import bodyParser from 'body-parser';
// If you don't use cors, the api will reject request if u call it from Cloud functions
import cors from 'cors';
import { verifyEmail } from './middleware'

app.use(cors());
app.use(bodyParser.urlencoded({
    extended: true,
}));
app.use(bodyParser.json());

const app = express();
// If you use the above example for endpoint then here will be
// '/api/verifyemail/'
app.post('<PATH TO ENDPOINT>', verifyEmail, (req, res, next) => {
    res.json({
    status: 'success',
    data: res.locals.data
  });
next()
})

This endpoint will return back the full user object and will send the verification email to user.

I hope this helps.

@IngAjVillalon

This comment has been minimized.

Copy link

IngAjVillalon commented Dec 19, 2018

Apparently, there are many of us who are asking firebase developers to add this feature to Node.js firebase-admin SDK.
Please!

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