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

Add firebase authentication emulator to emulator suite #1677

Closed
vdjurdjevic opened this issue Sep 26, 2019 · 66 comments
Closed

Add firebase authentication emulator to emulator suite #1677

vdjurdjevic opened this issue Sep 26, 2019 · 66 comments

Comments

@vdjurdjevic
Copy link

Is it possible to emulate the authentication API for faster local development and end to end testing? With the Firestore emulator, we don't have to create different projects for different environments (dev, testing, staging, prod), we can just use emulator, seed data to it from JSON files in watch mode for development, or seeding for every test case, etc. To be able to write end to end test that has a user log in, navigation based on role, and then some action, we have to create a separate project to isolate test users, and also seed and clear users there for every test case.

@samtstern
Copy link
Contributor

@vladimirdjurdjevic thanks for raising this! This is on our list of things to do, but we have not yet decided on how much of the auth service to emulate and the best strategy to do it. Certain things (like anonymous auth) are very easy to emulate while others (like SMS authentication) are very hard.

But we definitely want to enable the end-to-end use case you mentioned!

Question: would you find it useful if there was a library that allowed you to locally mock a Firebase user for use with the emulators? Something like:

firebase.auth().setEmulatedUser({
   uid: 'abc123',
   // ...
});

@noelmansour
Copy link

@samtstern Would the call to setEmulatedUser() also trigger any functions.auth.user().onCreate() emulated cloud functions? If so, that would be very useful.

@vdjurdjevic
Copy link
Author

@samtstern That would help some testing scenarios for sure, but still require real instance for development. My idea is to avoid creating multiple firebase projects for different environments, and to be able to develop locally (offline perhaps). Another approach would be to support different environments for services. If we could create different environments for let's say firestore and auth under the same project, it would solve many issues. I know I can create project per environment, but that's a real pain in the ass. Configuring every environment, replicating data, etc. Ideally, I would like to be able to create one firebase project, and if I need dummy data for manual testing, I could just create a staging environment for firestore, and upload data there.

@samtstern
Copy link
Contributor

@noelmansour good question! That wouldn't be too hard to do, we'd probably want two different calls like signInAsEmulatedUser and signUpAsEmulatedUser where only the second triggers the function.

@samtstern
Copy link
Contributor

@vladimirdjurdjevic totally agree that a full-featured emulator is best. Could you explain what things you would need from a "real instance for development" that are not solved by being able to set the user locally?

@IlCallo
Copy link

IlCallo commented Oct 9, 2019

Question: would you find it useful if there was a library that allowed you to locally mock a Firebase user for use with the emulators?

Really useful, it would help a lot.
Right now I'm just setting to true the rule function validating logged user every time I have to test something locally, it's pretty unsafe yet probably the simpler thing I can do without emulated current user.

@ben-wall
Copy link

Yes this would be incredibly useful

@danielkcz
Copy link

danielkcz commented Oct 13, 2019

I also would like to unit test functions.auth.user().onCreate(). I suppose right now the best workaround is essentially export callback function passed to onCreate and supply fake user object to it.

@samtstern
Copy link
Contributor

samtstern commented Oct 13, 2019 via email

@danielkcz
Copy link

@samtstern That actually works along with Firestore emulator? Base on this I got the impression it's for a different purposes.

image
https://firebase.google.com/docs/functions/unit-testing#initializing

I certainly don't want online mode for unit testing, that would be a slowpoke. And I am not sure if I can access the Firestore emulator by "stubbing".

@samtstern
Copy link
Contributor

@FredyC thanks for pointing out those docs. The words used are confusing because the "modes" are not a switch you can enable, they're just describing strategies you can take.

If you were to start up the Firestore emulator alongside your unit tests, your code could definitely connect to it. If you set the FIRESTORE_EMULATOR_HOST environment variable the Firebase Admin SDK will automatically connect to the Firestore emulator (firebase emulators:exec does this for you).

@danielkcz
Copy link

danielkcz commented Oct 16, 2019

@samtstern I still have trouble connecting dots and how is all that going to help me to test functions.auth.user().onCreate()? I mean it's great that functions will connect to emulator instead of a production version, but that's only Firestore, right? How do I invoke user creation from tests to actually have the function code to kick in?

There seems to be some cryptic method makeUserRecord in the mentioned firebase-functions-test, but it doesn't make much sense how would that work or how to actually use it.

I tried calling auth.createUserWithEmailAndPassword() from the @firebase/testing package, but that's complaining about invalid API key, so I assume it would work with online version only.

When I search through org for that env variable you have mentioned, it's only in three places and none seems to be relevant to auth really. Unless it's hidden by some string concatenation.

I've been also browsing through the https://github.com/firebase/functions-samples but I found no examples of unit testing there.

Can you please make some sense of this? :)

@danielkcz
Copy link

I also have another case, somewhat opposite, where the cloud function code is using admin.auth().getUserByEmail() call. Surprisingly it doesn't end with the error, but I have no idea how I would create that user so it can be returned. Except of course mocking out the whole admin module, but that's crazy :)

@vdjurdjevic
Copy link
Author

@samtstern Sorry for the delay. When I say real instance, I mean real instance :D Ideally, I just want to swap config in my angular environment file for development, and to keep implementing auth features like my app is talking to real API, but it actually emulator running on my machine, and I can do it offline. Now, I understand challenges with sending SMS for example. It would be silly to expect the emulator to send real SMS to my phone, but you could maybe just print it to console (the content of SMS that would be sent). It's probably more hassle than the value with this. That's why I think that just supporting multiple environments per project could make stuff much better. It takes to much time to replicate configs between multiple projects for different environments. And also managing multiple service accounts for scripts to be able to seed data to different Firestore projects is a pain. That's why I taught that if we had a whole firebase platform emulated, we could use that for every non-production environment, and manage just one real firebase project. But maybe just supporting multiple environments per project is a better solution with an acceptable outcome.

@samtstern
Copy link
Contributor

@FredyC thanks for all your feedback! It's really helpful to us to see how confusing this can be. There are two main things people want to do in their tests related to auth:

  1. Sign in to use services like Firestore or Realtime Database without actually creating real users. Right now that's what something like setEmulatedUser would solve. It would just allow you to have a bogus auth token locally that the emulators would accept but it would be rejected by prod. More safety, more isolation, etc.
  2. Actually test authentication directly. This would have a few pieces:
    a. An auth emulator that responds to all the necessary API calls so that you can point the Auth SDKs at it.
    b. Integration between that emulator and the functions emulator so that .onCreate functions are triggered correctly.
    c. Auto-mocking inside the functions emulator so that admin.auth() points to the Auth emulator, just like we do for Firestore and RTDB today.

I think clearly (2) is the right story but I was trying to get a sense of how many people would be happy with (1) since it's substantially simpler to implement and maintain.

@danielkcz
Copy link

@samtstern I see. Correct me if I am wrong, but isn't the (1) already solved? I mean in tests I can just call the following and Firestore emulator will recognize me as that user so I can test against security rules. I haven't actually tried that yet, but looks promising so far :)

  const app = firebase.initializeTestApp({
    projectId,
    auth: {
      uid: 'owner'
    }
  })

The (2) definitely sounds very useful, but a lot more complex to tackle for sure. Sadly, my insight into the full product is so limited, I cannot really offer any useful ideas here.

I think it should be built incrementally. Instead of trying to cover all scenarios at once, build it based on known use cases and adding on the go. In my limited opinion emulating "user database" along with function triggers don't seem that hard to do.

@samtstern
Copy link
Contributor

@FredyC you're right that (1) is solved for use inside test suites. But the other use case is actually connecting your Android/iOS/Web app directly to the emulator for local development. In which case you can't use @firebase/testing.

@danielkcz
Copy link

I see. Honestly, it would be kinda superb if @firebase/testing could be used cross-platform instead of having separate solutions. I mean how hard it can be to redirect communication to the emulator? Isn't the FIRESTORE_EMULATOR_HOST for exactly that? Although I think something like FIREBASE_EMULATOR_HOST would be more appropriate if the emulator is going to have other services as well.

@danielkcz
Copy link

danielkcz commented Oct 17, 2019

@vladimirdjurdjevic I am thinking, wouldn't actually work to basically mock out the signInWithPhone method so you can control its behavior? Then you don't need to worry about an emulator and getting simulated SMS code in the console :)

Of course, then you need some way to authenticate toward the emulator for Firestore (and connect to it). Something like I outlined in previous #1677 (comment). There is an underlying method for generating unsecured tokens: https://github.com/firebase/firebase-js-sdk/blob/master/packages/testing/src/api/index.ts#L64. Not sure if that would work.

Of course, mocking 3rd party libraries is not always that easy, but once figured out, it can bring great results. Eventually, it could be extracted to some library to make it generally easier.

I am also thinking these signIn methods can throw quite a plethora of error codes which is something proper tests should take care of as well. That's easy to do with mocking as well.

@toddpi314
Copy link

toddpi314 commented Oct 21, 2019

@samtstern Extending the firebase.auth() namespace context with something topical like setEmulatedUser seems like an anti-pattern with the existing emulation strategies. Is that recommendation perhaps influenced by ease-of-extensibility on the package side?

Inline with the other emulators, I would expect a separate authentication service tier to be launched over HTTP and a client-side config can redirect the existing API surface to utilize.

I would much rather have eccentric AuthN cases return errors with the Admin and Client API minimally supporting CRUD for basic users over username/password. Heck, i think even starting with Custom Token support in the Admin and signInWithCustomToken would go really far. Maybe implement low hanging fruit with an API support matrix published in the docs.

To @FredyC point, the current strategy for integration Auth testing is to conditionally import the @firebase/testing in application code to route to the custom initializeTestApp call. This action both stresses package exclusion at build-time for project teams, and also spreads the emulator redirect configs across two package APIs (initializeTestApp and firestore.settings/functions.useFunctionsEmulator)

Hack the planet!

@danielkcz
Copy link

danielkcz commented Oct 21, 2019

the current strategy for integration Auth testing is to conditionally import the @firebase/testing in application code to route to the custom initializeTestApp call.

Um, I am calling that method inside tests. The trick is that regular initializeApp resides in index.ts which imports functions. It's being called when emulator starts, but when tests are executing, it's a different process and it doesn't conflict with each other. So there is really no burden of conditional import.

Of course, this might be different for testing auth in eg. web app where test runner would share the process with app code. However, with unit testing you don't usually import a whole app into a test. The initializeApp is probably done in some code that's not relevant to tests and is not imported at all.

@toddpi314
Copy link

@FredyC Agreed on the documented usages for unit tests. Was really speaking to full-app scenarios where the apis diverge and dynamic imports go off the documented map.

I just wanted to give you attribution for Honestly, it would be kinda superb if @firebase/testing could be used cross-platform instead of having separate solutions. digital high five

@diginikkari
Copy link

@FredyC thanks for all your feedback! It's really helpful to us to see how confusing this can be. There are two main things people want to do in their tests related to auth:

  1. Sign in to use services like Firestore or Realtime Database without actually creating real users. Right now that's what something like setEmulatedUser would solve. It would just allow you to have a bogus auth token locally that the emulators would accept but it would be rejected by prod. More safety, more isolation, etc.
  2. Actually test authentication directly. This would have a few pieces:
    a. An auth emulator that responds to all the necessary API calls so that you can point the Auth SDKs at it.
    b. Integration between that emulator and the functions emulator so that .onCreate functions are triggered correctly.
    c. Auto-mocking inside the functions emulator so that admin.auth() points to the Auth emulator, just like we do for Firestore and RTDB today.

I think clearly (2) is the right story but I was trying to get a sense of how many people would be happy with (1) since it's substantially simpler to implement and maintain.

@samtstern first of all I would ❤️to have this kind of emulation.

I would see no. 1 very useful for writing e2e tests. Currently I have to use real instance for authentication while I can use emulator for hosting, rules, firestore and functions.

I think it should be possible to use setEmulatedUser to mock user in same way it's done in firebase.initializeTestApp. It's should be possible to submit e.g. custom tokens and other user related data.

It should also be possible to get sample credentials which could be used in client app with firebase.auth().signInWithCredential(credential)

@dominikfoldi
Copy link

Thank you @vladimirdjurdjevic for bringing up this issue! We were looking for a solution for almost a year already.

We would like to see a real emulator for three things:

  1. e2e tests for the whole app, so we do not have to create different environments as @vladimirdjurdjevic said.
  2. Integration tests for the backend, where the APIs are called and the user should be validated at Firebase.
  3. All of our developers are using a central Firebase environment during development, which causes lots of collisions. Of course, every developer could create their own Firebase project but they still need to manage their test users in the Firebase dashboard which is not ideal. Also, we would like to develop our app offline that is not possible today because of the lack of a real emulator.

I hope that you will release something for us soon, it would make your product more valuable! Developer productivity is very important in such services!

@fandy
Copy link

fandy commented Mar 2, 2020

This seems to be a feature on many people's wish list. Firebase auth seems to be the #1 IDaaS right now pricing wise, so it's really a pain that you can't develop locally with it with Cloud Functions. Hope the FB team has updates for us soon! 🙏

Edit: Pinging @mbleigh from @firebase-ops since this thread is buried under issues...

@alexroldan
Copy link

I also have this error ... function ignored because the auth emulator does not exist or is not running.

this is triggered by this code:

functions.auth.user().onDelete()

any info on this ...

@dirkpostma
Copy link

dirkpostma commented May 2, 2020

@dominikfoldi

I agree with your points. One tip that might help you meanwhile:

Of course, every developer could create their own Firebase project but they still need to manage their test users in the Firebase dashboard which is not ideal.

You can seed and manage users programmatically using firebase admin SDK, e.g. auth().createUser, see also https://firebase.google.com/docs/auth/admin/manage-users

@quantuminformation
Copy link

quantuminformation commented Jul 10, 2020 via email

@rishisingh-dev
Copy link

Yes, my primary goal was updating the database with user information inside onCreate function.
But since I can't get displayName, I made a onCallable function, and did it.

Thanks.

@vdjurdjevic
Copy link
Author

@samtstern I am very happy to see you working on this :) Just started new project with firebase, and dev experience is already much better (with emulator ui and --inspect-functions option). Looking forward to see auth emulator in action :) Great job!

@quantuminformation
Copy link

another of the best thing is that I don't have to open chrome with no security

@HerrNiklasRaab
Copy link

@samtstern

Two month later, is it now possible to give a rough estimate?

We would like to ship beginning of the next year. We are now faced with the decision if we start to write integration tests against a real project or if we wait a few month for the auth emulator. Could you help us a bit here?

Best,

Niklas

@samtstern
Copy link
Contributor

Our policy at Firebase is not to give estimates on when something will launch unless we're 100% sure. Right now we're working hard on the Auth emulator but we're not close enough to being done to pick a launch date.

It's one of our top priorities, but I think you should not wait for us to start testing your app. Write your tests against prod today, switch them to target the emulator when it's available.

@HerrNiklasRaab
Copy link

Alright, thanks @samtstern. That helps!

@willbattel
Copy link
Contributor

Something we'd really like to use an auth emulator for is testing Cloud Functions and Firestore requests that involve Custom Claims on user tokens. We can sort of test custom claims with Firestore in the rules playground in the Firebase Console, but a full-fledged auth emulator would theoretically enable us to do much more in the way of testing when it comes to user tokens.

@samtstern
Copy link
Contributor

To be clear: we're committed to a full-fledged auth emulator that emulates the actual service endpoints (wherever possible). We're no longer considering something lightweight like my earlier comments suggested.

@fandy
Copy link

fandy commented Aug 26, 2020

@samtstern that's great to hear (along with the custom claims, since that's often used with third party integrations). Is there anywhere we can keep up with the progress/ETA?

@samtstern
Copy link
Contributor

@fandy no sorry we don't have anything to share yet...

@quantuminformation
Copy link

quantuminformation commented Aug 26, 2020 via email

@tv42
Copy link

tv42 commented Aug 26, 2020

To work around this, I experimented with importing @firebase/testing in the browser. That didn't really work. This, however, does: mangle @firebase/testing source to copy out the following slightly edited chunk:

import firebase from "firebase/app"
import * as component from "@firebase/component"
import * as util from "@firebase/util"
import { __awaiter, __generator } from "tslib"

function createUnsecuredJwt(auth) {
    // Unsecured JWTs use "none" as the algorithm.
    var header = {
        alg: 'none',
        kid: 'fakekid'
    };
    // Ensure that the auth payload has a value for 'iat'.
    auth.iat = auth.iat || 0;
    // Use `uid` field as a backup when `sub` is missing.
    auth.sub = auth.sub || auth.uid;
    if (!auth.sub) {
        throw new Error("auth must be an object with a 'sub' or 'uid' field");
    }
    // Unsecured JWTs use the empty string as a signature.
    var signature = '';
    return [
        util.base64.encodeString(JSON.stringify(header), /*webSafe=*/ false),
        util.base64.encodeString(JSON.stringify(auth), /*webSafe=*/ false),
        signature
    ].join('.');
}

function initializeApp(accessToken, options) {
    var _this = this;
    var app = firebase.initializeApp(options);
    if (accessToken) {
        var mockAuthComponent = new component.Component('auth-internal', function () {
            return ({
                getToken: function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
                    return [2 /*return*/, ({ accessToken: accessToken })];
                }); }); },
                getUid: function () { return null; },
                addAuthTokenListener: function (listener) {
                    // Call listener once immediately with predefined accessToken.
                    listener(accessToken);
                },
                removeAuthTokenListener: function () { }
            });
        }, "PRIVATE" /* PRIVATE */);
        app._addOrOverwriteComponent(mockAuthComponent);
    }
    return app;
}

export function initializeTestApp(options) {
  let accessToken = undefined
  if (options.auth !== undefined) {
    accessToken = createUnsecuredJwt(options.auth)
  }
  return initializeApp(accessToken, options)
}

Now in your app browser-side code, you can import that, and

  let app
  if (dev) {
    app = initializeTestApp({projectId: "test", auth: {uid: "testuser"}})
    app.firestore().settings({
      host: "localhost:8080",
      ssl: false,
    })
  } else {
    app = firebaseProduction.initializeApp({ firebase config here })
    app.firestore().settings({ firestore config here })
  }
  window.firebase = app

That works great! Now when I'm running in development, I have a local fake user that the emulator thinks is "testuser" (like the firestore security rules testing guide shows).

@laurentpayot
Copy link

Another workaround that I use is to stub your authentication (I'm using generated fake users for tests).

TypeScript example:

import type { User as AuthUser } from '@firebase/auth-types'
// not importing a type, but a module of types
import { auth as authTypes} from 'firebase/app'
type Auth = authTypes.Auth

export const authStub = {
    getUser(uid: string) {
        // for uids like `testuser1234uid`
        let n = uid ? uid.replace("testuser", '').replace("uid", '') : ''
        return Promise.resolve({
            uid,
            email: `test.user${n}@foo.com`,
            emailVerified: true,
            providerData: [{
                providerId: 'google.com',
                email: `test.user${n}@foo.com`,
                uid: `testuser${n}provideruid`,
                phoneNumber: null,
                displayName: `Test User ${n}`.trim(),
                photoURL: 'https://thispersondoesnotexist.com/image',
            }],
            metadata: {
                // https://firebase.google.com/docs/reference/admin/node/admin.auth.UserMetadata
                createTime: new Date().toUTCString(),
                lastSignInTime: new Date().toUTCString()
            },
            customClaims: {
                username: `testuser${n}`
            }
        })
    },
    deleteUser(uid: string) {
        return Promise.resolve()
    },
    async updateUser(uid: string, data: AuthUser) {
        let user = await this.getUser(uid)
        return { ...user, data }
    },
    setCustomUserClaims(uid: string, customUserClaims: Object): Promise<void> {
        return Promise.resolve()
    }
}

export const auth = <Auth><unknown>authStub

Also modify your rules as auth.token is not emulated. For instance:

const rules = fs.readFileSync(__dirname + '/src/firebase/firestore/firestore.rules', 'utf8')
const modifiedRules =
    rules
        .replace(/request\.auth\.token\.email_verified/g, "true")
        .replace(/request\.auth\.token\.firebase\.sign_in_provider/g, "'password'")

await firebase.loadFirestoreRules({ projectId, rules: modifiedRules })

It's working great for me. Hope it helps…

@samtstern
Copy link
Contributor

If you're following this thread and willing to be an Alpha tester of the Firebase Authentication Emulator, follow these steps:

  1. Sign up for the Firebase Alpha program: https://services.google.com/fb/forms/firebasealphaprogram/
  2. Send me an email at samstern@google.com, make sure to send it from the email address you will use for testing.

Please only do this if you're seriously interested in trying out an early-stage emulator and providing feedback! There will be bugs and some parts of the setup will be hard, but we want to know what you think.

@vdjurdjevic
Copy link
Author

@samtstern This is great news! I would love to try it, but I am going to production with my current project by the end of the week, so I can't afford to play with it at the moment. Will sign up for alpha as soon as I can :) Thanks for the great work.

@quantuminformation
Copy link

100% will want to try this! your the man Sam!

@danielcolgan
Copy link

@samtstern looking forward to trying it and help whatever I can!

@joaoeudes7
Copy link

joaoeudes7 commented Oct 19, 2020

I need use feature of auth to test functions locally from Android, the context of Auth is always null

@jornetsimon
Copy link

Good news, the Authentication Emulator is part of the fresh 8.14.0 release! 🙌🎊

Thank you for the hard work guys and @samtstern 💪

@quantuminformation
Copy link

awesome guys!

@samtstern
Copy link
Contributor

I'm just the messenger! The Auth emulator was 99% built by @yuchenshi ... and that's why I'm going to let him have the honor of closing this issue when he wakes up.

@nicoburns
Copy link

nicoburns commented Oct 27, 2020

Is there any documentation on this new emulator? (how to install, configure clients, etc)

P.S.Thanks a lot for all the hard work on this. This is going enable all sorts of cool stuff for us.

@samtstern
Copy link
Contributor

@nicoburns very soon! Official announcement, docs, and all that good stuff coming very shortly.

@vdjurdjevic
Copy link
Author

Great news! :) Can't wait to give it a try :)

@yuchenshi
Copy link
Member

I know you've been waiting for this, so let's just cut to the point:

  • Firebase Authentication Emulator is now available. You can get it by installing Firebase CLI >= v8.14.0.
  • Follow the Emulator Suite guide to get started, and connect your apps.
  • For exciting announcements like this and much much more, tune in to Firebase Summit livestream RIGHT NOW.**

**Shameless plug: I'm also doing a session on "How to set up CI using the Firebase Emulator suite" later today. Locating that on the session schedule is left as an exercise for the reader.


P.S. I really cannot take 99% of the credit since the Auth Emulator is of course teamwork. Various people at Firebase and Firebase developers (you) played a big part in this too. Thank you!

@vdjurdjevic
Copy link
Author

@yuchenshi Is it possible to export created users on exit, just like with firestore emulator?

@samtstern
Copy link
Contributor

@vdjurdjevic not yet, we're working on that.

This is a really popular issue and every update notifies 50-100 people. Since we have now released the Auth emulator I am going to lock this issue from future updates. If you have a question, bug, or feature request please start a new issue!

@firebase firebase locked as resolved and limited conversation to collaborators Nov 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests