firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.02k stars 932 forks source link

[FR] Verifying emulated ID Tokens and creating session cookies only works in the Functions Emulator #2764

Closed favsss closed 3 years ago

favsss commented 3 years ago

[REQUIRED] Environment info

firebase-tools: 8.14.1

Platform: Windows 10

[REQUIRED] Test case

Currently emulating a scenario where a signed in user sends userToken from frontend and backend decoded this token and retrieves uid for fetching data from database.

const firebase = require('firebase/app');
require('firebase/auth');

const config = require('./config/firebaseConfig.json');

const firebaseConfig = {
  apiKey: config.apiKey,
  authDomain: config.authDomain,
  databaseURL: config.projectId,
  projectId: config.projectId,
  storageBucket: config.storageBucket,
  messagingSenderId: config.messagingSenderId,
  appId: config.appId,
  measurementId: config.measurementId
};

firebase.initializeApp(firebaseConfig);
firebase.auth().useEmulator("http://localhost:9099/");

const serviceAccountKey = require('./config/serviceAccountKey.json');

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

const databaseUrl = `http://localhost:9000/?ns=${config.projectId}`;
admin.initializeApp({
  credential: admin.credential.cert(serviceAccountKey),
  databaseURL: databaseUrl
});

const perform = async() => {
  const email = "test3@gmail.com";
  const password = "password";
  await admin.auth().createUser({
    email: email,
    password: password
  });

  await firebase.auth().signInWithEmailAndPassword(email, password);
  const userToken = await firebase.auth().currentUser.getIdToken();

  // code fails here where admin is supposed to successfully verify id token
  await admin.auth().verifyIdToken(userToken);
};

perform();

[REQUIRED] Steps to reproduce

make sure that the following node packages are installed and firebase emulators for auth, firestore, and database are enabled:

 npm install -g firebase-tools
 npm install --save firebase
 npm install --save firebase-admin

[REQUIRED] Expected behavior

The userToken is decoded properly so that uid is extracted from the token for fetching data.

[REQUIRED] Actual behavior

It fails with Firebase ID token has no "kid" claim. The code is working normally except when using emulator.

WARNING: You are using the Auth Emulator, which is intended for local testing only.  Do not use with production credentials.
(node:24084) UnhandledPromiseRejectionWarning: Error: Firebase ID token has no "kid" claim. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
    at FirebaseAuthError.FirebaseError [as constructor]
mjroeleveld commented 3 years ago

I'm running into this as well. I can't verify idTokens created by the emulator.

samtstern commented 3 years ago

@favs-sama where are you running the admin code?

Right now verifyIdToken() for auth emulator tokens only works inside the Functions emulator. This is out of a concern for security, short-circuiting verifyIdToken() is obviously dangerous if it accidentally happens in production so right now we only allow it in the Functions emulator, which we can control fully.

favsss commented 3 years ago

@samtstern originally i'm running the admin code verifyIdToken() in a backend prehandler function for handling user authorization; so, no i'm not running this inside a firebase function. although is there any other way to approach this kind of testing if currently verifyIdToken() for auth emulator is only accessible via functions emulator?

samtstern commented 3 years ago

@favs-sama yeah right now the only way to test this is inside the Functions emulator. We do want to change this eventually but it requires us to build in a more secure version of verifyIdToken() that would be less dangerous to your server in the event of a configuration mistake!

So for now I'll consider this a feature request.

malcolmdeck commented 3 years ago

I've created b/172262218 to track this feature request internally

ricardoatsouza commented 3 years ago

Also running into this. Will be watching this issue.

samtstern commented 3 years ago

I changed the name of this issue to more accurately reflect the feature request and include the use case from #2770

AudunWA commented 3 years ago

I'd need to verify/decode the token with the emulator as well.

daniel-tucano commented 3 years ago

@favs-sama where are you running the admin code?

Right now verifyIdToken() for auth emulator tokens only works inside the Functions emulator. This is out of a concern for security, short-circuiting verifyIdToken() is obviously dangerous if it accidentally happens in production so right now we only allow it in the Functions emulator, which we can control fully.

I was planing to use the auth emulator to do tests without some weird workarounds required at the moment. I think that this behaviour (or at least some imitation of it) should be implemented for testing purposes. This also should have been mentioned at the documentation, would have saved me some time haha

patelnets commented 3 years ago

Does anyone have a workaround for this?

mjgerace commented 3 years ago

@samtstern just voicing my support for this - many, many engineers do not use https.callable and instead opt for express setups, myself included. Not being able to support this means that we have a lack of ability to test our client-server interaction through integration tests.

From an engineering perspective, shouldn't this be as easy as disabling the 'kid' claim check when a certain envvar is set to true (IE, an envvar that is only set when firebase is running emulator mode)? I don't particularly understand why this would be hard to facilitate.

samtstern commented 3 years ago

@mjgerace we know how to do this, but we're being extra careful about security. If your production server ever got into a situation where it thought it should disable/skip ID token verification then you'd have a big problem!

In order to have something in time for the launch of the Auth Emulator we compromised on a simple solution in the Node.js Admin SDK that is only enabled inside the Functions emulator. We are actively working on a longer-term solution that we're happier with and when we finish it we will bring it to all of our Admin SDKs (Node, Java, Go, Python, etc) so that you can develop on your own server.

mjgerace commented 3 years ago

@samtstern this makes total sense - is there any timeline for this work? In the meantime, would it be bad for our team to set an env var (FIREBASE_EMULATOR=1 yarn jest {test_file}) and otherwise workaround the issue in my actual auth middleware?

So long as I can modify the verifyIdToken() function, I could manually implement my suggested fix while I work on testing. Our app isn't in production and this would allow us to write tests without doing anything overly risky.

samtstern commented 3 years ago

@mjgerace we never offer timelines but this is something we're actively working on, it's not on the backlog. If you want to work around this issue on your own server and you're confident you know how, go for it!

daniel-tucano commented 3 years ago

Does anyone have a workaround for this?

At the moment when I'm running tests i change NODE_ENV environment variable to "test" and, based on that, change the auth token checking code to use the decode function from 'jsonwebtoken' module instead of firebase auth

nicoburns commented 3 years ago

@samtstern Couldn't this be as simple as checking for the presence of the FIREBASE_AUTH_EMULATOR_HOST environment variable? If you are using the auth emulator then it is safe to assume that you're not running in production (or you already have some very serious security issues).

samtstern commented 3 years ago

@nicoburns we use the presence of that environment variable to redirect any outbound HTTP requests the Admin SDK makes to the Auth API. However verifying ID Tokens and creating session cookies are mostly local operations that do not touch the Auth API. So changing how they work just based on the presence of an env var would create the possibility of someone remotely short-circuiting your Admin server's security, something we really really want to avoid.

We're working on changes to how the Admin SDK handles these operations that will make it safer to fix this issue.

tudor07 commented 3 years ago

any updates on this?

jasonho-lynx commented 3 years ago

The error seems to also happen despite using the Functions emulator - do you mean that it only works if verifyIdToken is used within a https.callable? I'm using Express in my Cloud Function instead

yuchenshi commented 3 years ago

This restriction is now lifted as of Node.js Admin SDK v9.5.0. Happy verifying emulator tokens!

Joebayld commented 3 years ago

This restriction is now lifted as of Node.js Admin SDK v9.5.0. Happy verifying emulator tokens!

Is there anything special we need to do for the firebase-admin to verify emulator tokens? I'm still getting the 'Firebase ID token has no "kid" claim.' error..

EDIT: I found the issue.. I had to set the FIREBASE_AUTH_EMULATOR_HOST env variable. However, when running in Docker, localhost doesn't work and you have to use a special url. The following works if you're doing tests in a docker image: docker run -e FIREBASE_AUTH_EMULATOR_HOST='host.docker.internal:9099' -p 8088:8080 my-image

athomasoriginal commented 3 years ago

Hey all! Glad to here this landed in Node Admin SDK. Is there an issue where I can track when this lands in Java Admin SDK?

yuchenshi commented 3 years ago

@athomasoriginal https://github.com/firebase/firebase-admin-java/issues/493 tracks the Java Admin SDK feature request.

vajahath commented 3 years ago

@yuchenshi Let's update the Firebase documentation. It still says

When running in any other environment, such as Cloud Run or your own server, these tokens will be rejected by the Admin SDK.

ref: https://firebase.google.com/docs/emulator-suite/connect_auth#id_tokens

yuchenshi commented 3 years ago

@vajahath We're updated the docs. Thanks for the heads up.

artooras commented 3 years ago

Hi. A silly question perhaps, but where does one set the FIREBASE_AUTH_EMULATOR_HOST environment variable? The documentation doesn't explicitly state it. In which file should it be set? Where should it be located? @Joebayld , you seem to have figured it out, so any advice would be really appreciated... :) Thanks!

v-kiniv commented 3 years ago

Hi. A silly question perhaps, but where does one set the FIREBASE_AUTH_EMULATOR_HOST environment variable? The documentation doesn't explicitly state it. In which file should it be set? Where should it be located? @Joebayld , you seem to have figured it out, so any advice would be really appreciated... :) Thanks!

It's environment variable, you can set it globally in ~/.bash_profile or in terminal before firebase command: FIREBASE_AUTH_EMULATOR_HOST=localhost:9099; firebase emulators:start.

For convenience use something like https://www.npmjs.com/package/dotenv, to separate configurations.

artooras commented 3 years ago

Thanks @v-kiniv . Only I made it work by setting the variable on the dev script as opposed to the emulators script:

"dev": "FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 next dev"

harmandeep-singh commented 3 years ago

This still existing in my case ,

    code: 'auth/argument-error',
    message: 'Firebase ID token has no "kid" claim. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'

i am using firebase latest : "firebase-admin": "^9.11.0", also set env. variable : FIREBASE_AUTH_EMULATOR_HOST=localhost:9099

const idToken="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJlbWFpbCI6InJvaGFuLmtoYWFuYWlvQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYXV0aF90aW1lIjoxNjI3NTY5NDA3LCJ1c2VyX2lkIjoiQ2VmQXV6ZlFsdG4yWkN2V1dIYU9rWE82YmhOMCIsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicm9oYW4ua2hhYW5haW9AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifSwiaWF0IjoxNjI3NTY5NDA3LCJleHAiOjE2Mjc1NzMwMDcsImF1ZCI6InNreWNybS0xYjYzOSIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9za3ljcm0tMWI2MzkiLCJzdWIiOiJDZWZBdXpmUWx0bjJaQ3ZXV0hhT2tYTzZiaE4wIn0.";

admin.auth().verifyIdToken(idToken).then((decodedToken) => {
        console.log("decode token", decodedToken);
})
v-kiniv commented 3 years ago

This still existing in my case ,

    code: 'auth/argument-error',
    message: 'Firebase ID token has no "kid" claim. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'

i am using firebase latest : "firebase-admin": "^9.11.0", also set env. variable : FIREBASE_AUTH_EMULATOR_HOST=localhost:9099

const idToken="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJlbWFpbCI6InJvaGFuLmtoYWFuYWlvQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYXV0aF90aW1lIjoxNjI3NTY5NDA3LCJ1c2VyX2lkIjoiQ2VmQXV6ZlFsdG4yWkN2V1dIYU9rWE82YmhOMCIsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicm9oYW4ua2hhYW5haW9AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifSwiaWF0IjoxNjI3NTY5NDA3LCJleHAiOjE2Mjc1NzMwMDcsImF1ZCI6InNreWNybS0xYjYzOSIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9za3ljcm0tMWI2MzkiLCJzdWIiOiJDZWZBdXpmUWx0bjJaQ3ZXV0hhT2tYTzZiaE4wIn0.";

admin.auth().verifyIdToken(idToken).then((decodedToken) => {
        console.log("decode token", decodedToken);
})

Make sure you've updated firebase-tools to the latest version: npm i -g firebase-tools

harmandeep-singh commented 3 years ago

This still existing in my case ,

    code: 'auth/argument-error',
    message: 'Firebase ID token has no "kid" claim. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.'

i am using firebase latest : "firebase-admin": "^9.11.0", also set env. variable : FIREBASE_AUTH_EMULATOR_HOST=localhost:9099

const idToken="eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJlbWFpbCI6InJvaGFuLmtoYWFuYWlvQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiYXV0aF90aW1lIjoxNjI3NTY5NDA3LCJ1c2VyX2lkIjoiQ2VmQXV6ZlFsdG4yWkN2V1dIYU9rWE82YmhOMCIsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicm9oYW4ua2hhYW5haW9AZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifSwiaWF0IjoxNjI3NTY5NDA3LCJleHAiOjE2Mjc1NzMwMDcsImF1ZCI6InNreWNybS0xYjYzOSIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS9za3ljcm0tMWI2MzkiLCJzdWIiOiJDZWZBdXpmUWx0bjJaQ3ZXV0hhT2tYTzZiaE4wIn0.";

admin.auth().verifyIdToken(idToken).then((decodedToken) => {
        console.log("decode token", decodedToken);
})

Make sure you've updated firebase-tools to the latest version: npm i -g firebase-tools

sorry ! it was my Bad , after set env. reload the console can help if some one else stuck in same situation. 😀😀

samlof commented 3 years ago

Any news on this for other SDKs? I'm developing with Go and came across this.

Not hard to bypass when you find this but takes an hour to figure out why and implement it

EDIT. Seems my problem was using old package. Firestore docs https://firebase.google.com/docs/firestore/quickstart#go tell to install the old GOPATH version and not /v4

harmandeep-singh commented 3 years ago

Everyone Having issue make sure by using : export FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 for linux before running your application , double check that set's on your env., Linux : printenv will be helpful.

aruntj commented 3 years ago

Any news on this for other SDKs? I'm developing with Go and came across this.

Not hard to bypass when you find this but takes an hour to figure out why and implement it

EDIT. Seems my problem was using old package. Firestore docs https://firebase.google.com/docs/firestore/quickstart#go tell to install the old GOPATH version and not /v4

Oh my! You're a life saver! Following the docs does lead go users right into this issue. Thanks for this. And thanks firebase team!

rromanchuk commented 1 year ago

Is there an implementation (or merge) reference of the type of branching FIREBASE_AUTH_EMULATOR_HOST is doing during jwt decoding of unsigned emulator tokens? AKA, I don't have a Admin SDK so decoding/verification fails so need to implement myself.

jalvini commented 1 year ago

This is still happening to me.

I am logging the user in using the following piece of code in Swift:

Auth.auth().signIn(withEmail: email, password: password)

I am then passing in an ID token to the backend web call like so:

let userIDToken = try await signIn.result?.user.getIDToken() ?? ""
await web.webCall(endpoint: userIDToken)

Finally, I am using Express/Cloud Run on the backend and verifying my token like this:

let idToken = await admin.auth().verifyIdToken(req.body.idToken);
const uid = idToken.uid;

console.log(uid);

However, I am getting the following error message:

FirebaseAuthError: Firebase ID token has no "kid" claim. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.

When I go to that URL, it tells me to do exactly what Im doing, so what am I doing wrong?

taeold commented 1 year ago

@jalvini If you are having issues, can you file a new bug?

HigorAlves commented 1 year ago

Even with the env var Im getting this error:

FirebaseAppError: Error while making request: connect ECONNREFUSED ::1:9099. Error code: ECONNREFUSED

wesharper commented 1 year ago

Even with the env var Im getting this error:

FirebaseAppError: Error while making request: connect ECONNREFUSED ::1:9099. Error code: ECONNREFUSED

I had to change mine to 127.0.0.1:9099 vs localhost:9099. However, I'm now having an issue where the firebase auth emulator cannot find the user corresponding to the identifier in the JWT.

FirebaseAuthError: There is no user record corresponding to the provided identifier.

Mikephii commented 2 months ago

any update on this for the go v4 sdk? is it safe to use "firebase.google.com/go" instead of v4?

abdushkur commented 1 month ago

I implemented like this:

async function getIdTokenByEmailAndPassword(email, password) {
  const isRunningInEmulator = process.env.FIREBASE_AUTH_EMULATOR_HOST !== undefined;
  let baseUrl = `https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=${process.env.GOOGLE_API_KEY}`;
  if(isRunningInEmulator){
    baseUrl = `http://${process.env.FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${process.env.GOOGLE_API_KEY}`;
  }
  const data = {
    email: email,
    password: password,
    returnSecureToken: true
  };
  try {
    const response = await axios.post(baseUrl, data, {
      headers: {
        'Content-Type': 'application/json'
      }
    });
    return response.data;
  } catch (error) {
    throw Error(error.response ? error.response.data.error.message : error.message);
  }
}

async function getIdTokenByCustomToken(customToken) {
  const isRunningInEmulator = process.env.FIREBASE_AUTH_EMULATOR_HOST !== undefined;
  let baseUrl = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${process.env.GOOGLE_API_KEY}`;
  if(isRunningInEmulator){
    baseUrl = `http://${process.env.FIREBASE_AUTH_EMULATOR_HOST}/identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${process.env.GOOGLE_API_KEY}`;
  }
  const data = {
    token: customToken,
    returnSecureToken: true
  };
  try {
    const response = await axios.post(baseUrl, data, {
      headers: {
        'Content-Type': 'application/json'
      }
    });
    return response.data;
  } catch (error) {
    throw Error(error.response ? error.response.data.error.message : error.message);
  }
}