firebase / firebase-admin-node

Firebase Admin Node.js SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
1.6k stars 359 forks source link

[FR] Verify password server-side #1141

Open JaapWeijland opened 3 years ago

JaapWeijland commented 3 years ago

Is your feature request related to a problem? Please describe. I want to create a GraphQL API through which everything server-side is handled, including email-password authentication. Also, I want to make use of Firebase Authentication. I am able to register users with their email and password server-side, however, I am not able to verify their password when they are logging in through the GraphQL API, since the auth module of the node admin sdk does not support 'signing in', or checking the password in any other way server-side. I know I am able to recreate such a use case by letting the user login with the client side sdk, and after that, sending an id token to authenticate themselves, but that would require the client to have firebase installed, which is something I'd like to avoid.

Describe the solution you'd like Something like const userRecord = await auth().getUserByEmailAndPassword(email, password) or const isPasswordValid = await user.verifyPassword(password)

Describe alternatives you've considered Using an ID token sent from the client, but that would require you to install firebase on the client.

google-oss-bot commented 3 years ago

I found a few problems with this issue:

hiranya911 commented 3 years ago

We have no immediate plans to support client auth operations in the Admin SDK (although this is something that gets requested every now and then). There are couple of ways you can implement this use case server-side today:

  1. Use the client SDK in your backend app. We use this approach in our own Node.js integration tests: https://github.com/firebase/firebase-admin-node/blob/1862342636e816b61337262254cdfe0cc410c640/test/integration/auth.spec.ts#L335
  2. Use the REST API. We use this approach in integration tests of other languages: https://github.com/firebase/firebase-admin-java/blob/d05cb36f278f66d707184f243cbd7048f7712964/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java#L973-L988
bojeil-google commented 3 years ago

@hiranya911 is correct. For now, you can either use the Node.js client SDK or the REST API to do so.

Itshardtopickanusername commented 3 years ago

@bojeil-google Is this the REST API you are referring to? https://firebase.google.com/docs/reference/rest/auth#section-api-usage

If so, is this still being maintained / up to date?

While testing firebase Auth I noticed that, firebase library itself makes request to: https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[API_KEY]

However the REST API equivalent of this is: https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[API_KEY] I can't help but notice the differences in the versions of these APIs.

is it safe to use the REST API sign in option in production?

Or this? https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword#request

bojeil-google commented 3 years ago

The REST APIs are safe to use in production. We recommend the new format: https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[API_KEY] Over the legacy format: https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=[API_KEY]

The legacy format is still maintained. They both map to the same endpoint. However, there are new https://identitytoolkit.googleapis.com/v2/ endpoints which do not have an equivalent API in https://www.googleapis.com/identitytoolkit/v3/relyingparty/...

JaapWeijland commented 3 years ago

FYI, I resorted to installing the client SDK on the server, which works but feels like a hack.

penx commented 2 years ago

which works but feels like a hack

My understanding is that, in general, the client side SDK is stateful.

i.e. signing in in a JS module that uses firebase will sign in for all other JS modules that use the same firebase import. On a server, this means signing in during a request handler from one user will also sign in on a request from another user.

Perhaps if the client side SDK is not being used for anything else, then this "sign in" is ok as it's not using this state elsewhere.

However, as requests handlers can be async, this means multiple sign ins can happen at once. Can we be sure that the client side SDK will handle this securely on the server?

e.g. the following code contains async methods, can we be sure that multiple simultaneous requests wouldn't impact each other (e.g. users getting signed in as someone else because the request happened at the same time)

https://github.com/firebase/firebase-js-sdk/blob/26f9303669b301c10f3e7cd2e1002c200f4cf9a5/packages/auth/src/auth.js#L1693-L1703

If the client side SDK hasn't been designed and tested for this, I think it would be sensible to avoid it - even more reason that it would be great to have this feature from firebase-admin.

marcelino-borges commented 1 year ago

For the interest of whomever has reached this issue seeking for a solution, I've got success implementing both signin and refreshToken using Google's Identity Provider API, following the example provided in this LINK by @hiranya911 , which is Java, but is enough to translate it to Node.

I have a NodeJS API doing this directly with Google's API.

Check this function:

import { Request, Response } from "express";
export const validateUserCredentialsAndGetUserIdFromGoogle = async (
  email: string,
  password: string
): Promise<string | null> => {
  // process.env.GOOGLE_API_KEY = API KEY I got from my Google Cloud panel 
  //     > APIs & Services
  //     > Credentials 
  //     > API KEYS
  //     > Browser key (auto created by Firebase)
  const apiKey = process.env.GOOGLE_API_KEY;

  // process.env.GOOGLE_IDENTITY_BASE_URL = https://www.googleapis.com/identitytoolkit/v3/relyingparty
  const verifyPasswordUrl = `${process.env.GOOGLE_IDENTITY_BASE_URL}/verifyPassword?key=${apiKey}`;

  if (!verifyPasswordUrl?.length || !apiKey?.length) return null;

  const response: AxiosResponse = await axios.post(verifyPasswordUrl, {
    email,
    password,
    returnSecureToken: true, // This returns the refresh token
  });

  if (parseInt(response.status.toString()) === 200) {
    const userId = response.data.localId ?? null;

    return userId;
  }

  return null;
};

After I get my user ID from this function, I use it to create a custom token, where I can add new claims etc (just as described in Firebase docs), like so:

  import { getAuth } from "firebase-admin/auth";

  const additionalClaims = {
    premium: false,
  };
  const customToken = await getAuth().createCustomToken(
    userId,
    additionalClaims
  );

For the refresh token I have something like (which I can use to both refresh the token and just verify if the token is valid):

  import { Request, Response } from "express";

  export const refreshToken = async (token: string): Promise<string | null> => {
    const apiKey = process.env.GOOGLE_API_KEY;
    const verifyTokenUrl = `${process.env.GOOGLE_IDENTITY_BASE_URL}/verifyCustomToken?key=${apiKey}`;

    if (!apiKey?.length || !verifyTokenUrl?.length) return null;

    const response: AxiosResponse = await axios.post(verifyTokenUrl, {
      token,
      returnSecureToken: true,
    });

    return response.data.idToken ?? null;
  };

Of course in my project I decoupled a few things to make it cleaner, but here I'm putting it all together to make it objective for you.

Good luck!

risalfajar commented 1 year ago

If you're using Firebase Emulator, then your only option is to use Node Client SDK, which have problems explained by @penx above.

I think I'll go with storing copies of hashed users password in Firestore. :(

Hope Admin SDK have this feature soon.

sstylianides commented 1 year ago

+1 to have the server verify passwords and sign in users. Hoping this gets added soon

eduardolat commented 9 months ago

We have no immediate plans to support client auth operations in the Admin SDK (although this is something that gets requested every now and then). There are couple of ways you can implement this use case server-side today:

  1. Use the client SDK in your backend app. We use this approach in our own Node.js integration tests: https://github.com/firebase/firebase-admin-node/blob/1862342636e816b61337262254cdfe0cc410c640/test/integration/auth.spec.ts#L335
  2. Use the REST API. We use this approach in integration tests of other languages: https://github.com/firebase/firebase-admin-java/blob/d05cb36f278f66d707184f243cbd7048f7712964/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java#L973-L988

Do the rest of the API have any rate limits? I mean, all our users log in from our server IP, maybe it crashes with many requests.

taitasu555 commented 2 months ago

+1