kreait / firebase-tokens-php

A PHP library to work with Firebase tokens
MIT License
223 stars 33 forks source link

signInWithCustomToken #12

Closed LarryBarker closed 5 years ago

LarryBarker commented 5 years ago

Hello, thanks for putting this and your other package together. I notice there is no method to sign back into Firebase with a custom token. The docs show an example using the method signInWithCustomToken and I was wondering if there is a way to implement this?

I have a Laravel backend where our authentication is taking place. I am trying to build a way for our mobile app to authenticate to the Laravel app and have the Laravel app generate a Firebase token the mobile app can use to access the real time db/Firestore. Is this something that can be accomplished?

I have been able to generate the token using this code:

$serviceAccount = ServiceAccount::fromJsonFile(DIR . '/firebase-adminsdk-baomg-ed357f2f41.json');

$firebase = (new Factory)
    ->withServiceAccount($serviceAccount)
    ->create();

$auth = $firebase->getAuth();

$uid = 'AE0xzvmtu7Ox85S9jYJ0zt7Y8yH2';
$additionalClaims = [
    'premiumAccount' => true,
];

$customToken = $firebase->getAuth()->createCustomToken($uid, $additionalClaims);

$customTokenString = (string) $customToken;

``

but when I try to authenticate using this code:

try {
        $verifiedIdToken = $firebase->getAuth()->verifyIdToken($customTokenString);
    } catch (InvalidToken $e) {
        echo $e->getMessage();
    }

    $uid = $verifiedIdToken->getClaim('sub');
    $user = $firebase->getAuth()->getUser($uid);

    dd($user);

I am gettting: message": "The header \"kid\" is missing.",

From what I have read, it seems the verifyIdToken method is used to verify a valid token already on Firebase and I am looking for a way to validate the custom token generated by my Laravel backend auth.

Thanks again, I look forward to any advice or suggestions you may have.

jeromegamez commented 5 years ago

Hey Larry!

I had a method signInWithCustomToken() in a previous release of the SDK (not here in the tokens package, though), but removed it to emphasize that on a server, we are in the context of an administrative environment and some people were confused on why it didn't work as they expected or why they weren't able to sign in with a Facebook or Google Auth token.

That being said, you can't verify a custom token, only ID Tokens can be verified. The flow here would be:

  1. You create a custom token through the method you're already using, and the token is signed with your Service Account's private key.
  2. You exchange the custom token with an ID token by sending the custom token to Firebase so that they can confirm that the UID belongs to your project and that the Service Account is allowed to access the project.
  3. The API returns the ID token, which contains the kid header and which can be verified via the verifyIdToken() method.
  4. Then you can send the ID Token to your client, by casting the result of verifyIdToken() method to a string (the method returns an instance of \Lcobucci\JWT\Token (see https://github.com/lcobucci/jwt/tree/3.2)

Long story short, you can see an example in action at https://github.com/kreait/firebase-php/blob/master/tests/Integration/AuthTest.php#L145-L159 (I'm dogfooding the SDK in my own Integration Tests 😅)

LarryBarker commented 5 years ago

Interesting. So it sounds like we not even need to worry about getting the ID token back from the mobile client if the mobile client can use the custom token to authenticate on their end. Am I understanding that correctly?

I'm not concerned about the Firebase user doing anything to the real time db/Firestore from the Laravel app; the mobile client would be handling all the interaction there.

Thanks for the quick reply, let me know what else you may think.

jeromegamez commented 5 years ago

I believe you're correct, I thought just now of looking up https://firebase.google.com/docs/auth/ios/custom-auth and https://firebase.google.com/docs/auth/android/custom-auth , and it seems indeed that creating and sending over the custom token should definitely be enough \o/

LarryBarker commented 5 years ago

Sweet, that definitely simplifies my part. I modified my code a bit using your test and have this now:

$serviceAccount = ServiceAccount::fromJsonFile(__DIR__ . '/bg-rep-firebase-adminsdk-baomg-ed357f2f41.json');

    $firebase = (new Factory)
        ->withServiceAccount($serviceAccount)
        ->create();

    $auth = $firebase->getAuth();

    $customToken = $firebase->getAuth()->createCustomToken('AE0xzvmtu7Ox85S9jYJ0zt7Y8yH2');

    $idTokenResponse = $auth->getApiClient()->exchangeCustomTokenForIdAndRefreshToken(
        $customToken
    );

    $idToken = json_decode($idTokenResponse->getBody()->getContents(), true)['idToken'];

    try {
        $verifiedIdToken = $auth->verifyIdToken($idToken);
    } catch (InvalidToken $e) {
        echo $e->getMessage();
    }

    $uid = $verifiedIdToken->getClaim('sub');
    $user = $firebase->getAuth()->getUser($uid);

    dd($user);

This will take an existing user and create a custom token in the Laravel backend, and then exchange it for a ID token which I'm assuming could be used to authenticate back in the Firebase app. However, knowing the UUID of the user eliminates the need for this because you already have the user. As soon as our mobile guy has a minute we're going to try using the custom token to authenticate in the mobile app. I'll keep you posted if you're interested.

jeromegamez commented 5 years ago

Yes, please! But if you're using the Firebase Client SDK in your mobile apps, I'm not sure you need to exchange the custom token for an ID token anymore, the client SDK on the mobile device can do that (unless I miss a step in the process) - so as far as I see it at the moment, you need to create the custom token on the backend to be able to sign it with your service account's private key, but if you send that back to your mobile clients, the should be able to use the custom token directly to sign into Firebase.

The ID token is usually only needed if you want to use the access token to connect to the Firebase via their HTTP APIs.

(And the ID Token verification is usually meant to verify an ID token that has already been created by a client, to verify that the client token is legit).

So, no ID token verification on the backend needed, right? (And no code changes in the Tokens PHP Library, I would prefer not to have to touch it before the next refactoring 😅)

Jmunapo commented 4 years ago
Maybe too late but could [SOLVED] someone's issue
Scenario

Authenticate a NodeJS server with (firebase-admin) to access laravel endpoint for example updating user data

const admin = require( "firebase-admin" );
const axios = require( 'axios' ).default;

//Separate module
const refreshedToken = require( './init-firebase' );

const _axios = axios.create( {
            baseURL: 'https://example-laravel-server.com/api/server'
     } );

async setupAxiosHeaders () {
       const auth = admin.auth();
        const custom =  await auth.createCustomToken( 'some-firebase-uid' ) 
        const token = await refreshedToken(custom);
        _axios.defaults.headers.common[ 'Authorization' ] = `Bearer ${ token }`;
    }

//App Init
(async () => {
await setupAxiosHeaders();

const { data } = await _axios.get('/me');
console.log('Authenticated me:', data);
//Your code
})();

init-firebase.js


const firebase = require( 'firebase/app' )
require( 'firebase/auth' )
const config = {
   apiKey: "api-key",
  authDomain: "project-id.firebaseapp.com",
  databaseURL: "https://project-id.firebaseio.com",
  projectId: "project-id",
  storageBucket: "project-id.appspot.com",
  messagingSenderId: "sender-id",
  appId: "app-id",
  measurementId: "G-measurement-id",
}

firebase.initializeApp( config )

module.exports = refreshToken = async ( token ) => {
    try {
        const firebaseToken = await firebase.auth().signInWithCustomToken( token )
        return firebaseToken.user.getIdToken();
    } catch ( error ) {
        console.log( error );
    }
}