首页 > 解决方案 > Firebase: Sign in with a token

问题描述

Firebase question

My goal is to keep a user signed in a desktop app made primarily with NodeJS

  1. User signs in
  2. Store some kind of token to sign user in later
  3. When user re-opens app, sign user in with that token if available
  4. Token can expire / be invalidated

Currently, it seems Firebase relies on cookies of some sort. Is there a way to sign in a user without their email/password again?

I am currently trying to use Firebase authentication as part of my Electron app where I cannot have the Firebase authentication code be part of a renderer process which would normally have access to browser-level storage.

The ideal situation would be something like this that works:

    const auth_token = await user.getIdToken();

    firebase
      .auth()
      .signInWithCustomToken(auth_token) // But maybe some other method?
      .then((user) => {
        console.log(user);
      })

How would you do this in a NodeJS app that has no access to browser storage such that the user does not need to continuously login?

Referencing this issue: Persist Firebase user for Node.js client application

fwiw, I don't wish to share these tokens with another application.

Electron question

A slightly different question: Can the main process in Electron store cookies that Firebase could access?

Recently read this: https://github.com/firebase/firebase-js-sdk/issues/292

标签: firebaseelectron

解决方案


我对此的解决方案是:

  1. 通过电子邮件/密码获取刷新令牌(寿命约 1 年)
  2. 使用刷新令牌获取 id_token(短期)
  3. 获取带有 id_token 的自定义令牌(短期:~1 小时)
  4. 使用自定义令牌登录
  5. 在本地保存刷新令牌 - 永远不要共享它

所以,像这样:


import Store from 'electron-store';
import firebase from 'firebase';
import * as request from 'request-promise';

const store = new Store({ name: 'config' });

function logout() {
  const user = firebase.auth().currentUser;
  store.delete('refresh_token');

  if (user) {
    return firebase.auth().signOut();
  }
}

// https://firebase.google.com/docs/reference/rest/auth#section-create-email-password
function signup(email, password) {
  return request
    .post({
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      url: `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${firebase_config['apiKey']}`,
      body: `email=${email}&password=${password}&returnSecureToken=true`,
      json: true,
    })
    .then((res) => {
      store.set({ refresh_token: res.refreshToken });
      return login_with_id_token(res.idToken);
    });
}

// Generates a refresh_token that we later use & save
async function login_with_email(email: string, password: string) {
  const res = await request.post({
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
    url: `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${firebase_config['apiKey']}`,
    body: `email=${email}&password=${password}&returnSecureToken=true`,
    json: true,
  });

  const refresh_token = res.refreshToken;
  store.set({ refresh_token });
  console.log(store.path);
  return login_with_refresh_token(refresh_token);
}

/**
 * Needed to acquire a refresh_token
 * @param refresh_token string
 */
function get_id_token(refresh_token) {
  return request.post({
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
    url: `https://securetoken.googleapis.com/v1/token?key=${firebase_config['apiKey']}`,
    body: `grant_type=refresh_token&refresh_token=${refresh_token}`,
    json: true,
  });
}

/**
 * Generates a custom token we can use to sign in given an id_token
 * @param id_token string
 */
function get_custom_token(id_token) {
  return request.get({
    url: `https://us-central1-${firebase_config['projectId']}.cloudfunctions.net/create_custom_token?id_token=${id_token}`,
    json: true,
  });
}

function login_with_id_token(id_token) {
  if (id_token) {
    return get_custom_token(id_token).then((token) => {
      // console.log(`Retrieved custom token: ${custom_token}`);
      return firebase.auth().signInWithCustomToken(token);
    });
  }
}

/**
 * If token is null, it attempts to read it from disk otherwise
 * it will use the one supplied to login
 * @param token string || null
 */
async function login_with_refresh_token(token = null) {
  let id_token = null;
  let refresh_token = token;

  if (!refresh_token) {
    refresh_token = store.get('refresh_token');
    store.get('refresh_token', null);
    // console.log('Using a cached refresh token...');
  }

  if (refresh_token) {
    const res = await get_id_token(refresh_token);
    if (res) {
      id_token = res['id_token'];
      return login_with_id_token(id_token);
    }
  }
}

// Purposely attempt to login without a refresh_token in case it's on disk
function attempt_login() {
  return login_with_refresh_token(null);
}

Firebase 云功能:

exports.create_custom_token = functions.https.onRequest(async (req, res) => {
  const id_token = req.query.id_token;
  const user = await admin.auth().verifyIdToken(id_token);
  if (user) {
    const custom_token = await admin.auth().createCustomToken(user.uid);
    res.setHeader('Content-Type', 'application/json');
    res.status(200).send(custom_token);
  } else {
    res.sendStatus(500);
  }
});

推荐阅读