CustomToken не работает при выдаче через метод "createCustomToken" - PullRequest
1 голос
/ 08 ноября 2019

Я использую проверку подлинности Firebase для своего приложения и использую ее для аутентификации пользователей во внутреннем API с использованием токенов JWT. В интерфейсе API я настроил JWT-секрет, который представляет собой асимметричные ключи, извлеченные из этого URL:

https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com

Все это работает нормально. Недавно мне нужно было создать облачную функцию, которая также должна вызывать API-интерфейс. Для этого я использую функциональность для создания пользовательского токена, найденного здесь:

https://firebase.google.com/docs/auth/admin/create-custom-tokens

Это создает мой токен с правильными пользовательскими утверждениями

          let additionalClaims = {
             'x-hasura-default-role': 'admin',
             'x-hasura-allowed-roles': ['user', 'admin']
           }     

        admin.auth().createCustomToken(userId,additionalClaims).then(function (customToken) {
        console.log(customToken);

        response.end(JSON.stringify({
          token: customToken
        }))
      })
      .catch(function (error) {
       console.log('Error creating custom token:', error);
    });

однако, когда я пытаюсь использовать его против внутреннего API, я получаю ошибку «JWTInvalidSignature». В своей облачной функции я указываю учетную запись службы, которая есть в моем проекте Firebase, но, похоже, это не помогает. Когда я просматриваю декодированные два токена, они определенно появляются из разных сервисов.

CustomToken

     {
      "aud": 
       "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
       "iat": 1573164629,
       "exp": 1573168229,
       "iss": "firebase-adminsdk-r2942@postgrest-b4c8c.iam.gserviceaccount.com",
       "sub": "firebase-adminsdk-r2942@postgrest-b4c8c.iam.gserviceaccount.com",
       "uid": "mikeuserid",
        "claims": {
           "x-hasura-default-role": "admin",
           "x-hasura-allowed-roles": [
           "user",
           "admin"
        ]
       }
    }

TOKEN из FireBase Auth

 {
      "role": "webuser",   
      "schema": "customer1",
      "userid": "15",
      "claims": {
      "x-hasura-default-role": "user",
      "x-hasura-allowed-roles": [
      "user",
      "admin"
  ],
    "x-hasura-user-id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
    "x-hasura-dbuserid": "15"
   },
   "iss": "https://securetoken.google.com/postgrest-b4c8c",
   "aud": "postgrest-b4c8c",
   "auth_time": 1573155319,
   "user_id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
   "sub": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
   "iat": 1573164629,
   "exp": 1573168229,
   "email": "johnny1@gmail.com",
   "email_verified": false,
   "firebase": {
    "identities": {
     "email": [
    "johnny1@gmail.com"
  ]
  },
    "sign_in_provider": "password"
  }
 }

Как я могу получить этоcustomToken для работы с существующими секретными ключами JWT, которые я настроил ??

1 Ответ

0 голосов
/ 08 ноября 2019

Как описано в Аутентификация Firebase: пользователи в проектах Firebase: токены аутентификации , токены из Firebase Auth и пользовательских SDK Admin не совпадают, несовместимы друг с другом и проверяются по-разному.


Отредактированный ответ после уточнения:

Поскольку вы пытаетесь идентифицировать экземпляр облачных функций в качестве вызывающей стороны вашего стороннего API, вы можете использовать дваподходы.

В обоих нижеприведенных методах вы бы вызывали свой API, используя postToApi('/saveUserData', { ... }); в каждом примере. Вероятно, вы также можете объединить / поддержать оба подхода на стороне сервера.

Метод 1: использовать пару открытый-закрытый ключ

Для этой версии мы используем веб-токен JSON для подтверждения того, что вызовисходит из экземпляра Cloud Functions. В этой форме кода файл 'private.key' развертывается вместе с вашей функцией, а его открытый ключ хранится на стороннем сервере. Если вы вызываете свой API очень часто, рассмотрите возможность кэширования файла «private.key» в памяти, а не читайте его каждый раз.

Если вы когда-нибудь захотите аннулировать этот ключ, вам придется заново развернуть все свои функциикоторые используют это. В качестве альтернативы, вы можете изменить вызов fileRead() и сохранить его в Firebase Storage ( secure it - не может быть прочитан ни одним, доступен для записи backend-admin). Что позволит вам периодически обновлять закрытый ключ, просто заменяя файл.

  • Плюсы: только один удаленный запрос
  • Минусы: обновление ключей может быть сложным
const jwt = require('jsonwebtoken');
const rp = require('request-promise-native');
const functionsAdminId = 'cloud-functions-admin';

function getFunctionsAuthToken(jwtOptions) {
  jwtOptions = jwtOptions || {};
  return new Promise((resolve, reject) => {
    // 'private.key' is deployed with function
    fs.readFile('private.key', 'utf8', (err, keyData) => {
      if (err) { return reject({src: 'fs', err: err}); }

      jwt.sign('cloud-functions-admin', keyData, jwtOptions, (err, token) => {
        if (err) { return reject({src: 'jwt', err: err}); }
        resolve(token);
      });
    });
  });
}

Пример использования:

function postToApi(endpoint, body) {
  return getFunctionsAuthToken()
    .then((token) => {
      return rp({
          uri: `https://your-domain.here${endpoint}`,
          method: 'POST',
          headers: {
            Authorization: 'Bearer ' + token
          },
          body: body,
          json: true
        });
    });
}

Если вы используете экспресс на своем сервере, вы можете использовать express-jwt для десериализации токена. При правильной настройке req.user будет 'cloud-functions-admin' для запросов от ваших облачных функций.

const jwt = require('express-jwt');

app.use(jwt({secret: publicKey});

Метод 2. Добавление пользователя только для облачных функций

Альтернативой является избеганиепублично-частный ключ с использованием Firebase Auth. Это будет иметь компромисс потенциально медленнее.

  • Плюсы: управление ключами не требуется, легко проверить пользователя на сервере
  • Минусы: замедлено из-за вызовов Firebase Auth (1-2)
const admin = require('firebase-admin');
const rp = require('request-promise-native');
const firebase = require('firebase');
const functionsAdminId = 'cloud-functions-admin';

function getFunctionsAuthToken() {
  const fbAuth = firebase.auth();
  if (fbAuth.currentUser && fbAuth.currentUser.uid == uid) {
    // shortcut
    return fbAuth.currentUser.getIdToken(true)
      .catch((err) => {src: 'fb-token', err: err});
  }

  return admin.auth().createCustomToken(functionsAdminId)
      .then(function(customToken) {
        return fbAuth.signInWithCustomToken(token)
          .then(() => {
            return fbAuth.currentUser.getIdToken(false)
              .catch((err) => {src: 'fb-token', err: err});
          })
          .catch((err) => {src: 'fb-login', err: err});
      })
      .catch((err) => {src: 'admin-newtoken', err: err});
}

Пример использования:

function postToApi(endpoint, body) {
  return getFunctionsAuthToken()
    .then((token) => {
      return rp({
          uri: `https://your-domain.here${endpoint}`,
          method: 'POST',
          headers: {
            Authorization: 'Bearer ' + token
          },
          body: body,
          json: true
        });
    });
}

На вашем сервере вы должны использовать следующую проверку:

// idToken comes from the received message
admin.auth().verifyIdToken(idToken)
  .then(function(decodedToken) {
    if (decodedToken.uid != 'cloud-functions-admin') {
      throw 'not authorized';
    }
  }).catch(function(error) {
    // Handle error
  });

Или, если вы используете экспресс,Вы можете прикрепить его к промежуточному программному обеспечению.

app.use(function handleFirebaseTokens(req, res, next) {
  if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
    var token = req.headers.authorization.split(' ')[1];
    admin.auth().verifyIdToken(idToken)
      .then((decodedToken) => {
        req.user = decodedToken;
        next();
      }, (err) => {
        //ignore bad tokens?
        next();
      });
  } else {
    next();
  }
});

// later on: req.user.uid === 'cloud-functions-admin'


Исходный ответ:

Если ваш клиент использует для аутентификации Firebase из SDK для входа в систему и вашего сервераиспользует Admin SDK, вы можете использовать токен идентификатора клиента в облачной функции, чтобы поговорить с вашим сервером, чтобы подтвердить пользователя, по сути "передавая посылку".

Клиентская сторона

firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
  // Send token to your cloud function
  // ...
}).catch(function(error) {
  // Handle error
});

Облачная функция

// idToken comes from the client app
admin.auth().verifyIdToken(idToken) // optional (best-practice to 'fail-fast')
  .then(function(decodedToken) {
    // do something before talking to your third-party API
    // e.g. get data from database/secret keys/etc.
    // Send original idToken to your third-party API with new request data
  }).catch(function(error) {
    // Handle error
  });

Сторонний API

// idToken comes from the client app
admin.auth().verifyIdToken(idToken)
  .then(function(decodedToken) {
    // do something with verified user
  }).catch(function(error) {
    // Handle error
  });
...