Как описано в Аутентификация 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
});