(Обновлено) Использование Firebase-Admin в качестве промежуточного ПО для аутентификации в Express.js
Это обновленная версия этого поста, который я сделал в 2017 году. С тех пор firebase претерпела множество изменений и доработок. Это работает с 2019 года, и вы должны быть готовы к работе.
Вы, должно быть, слышали о простоте Firebase и о том, что это универсальное решение для управления базами данных, аутентификации и хранения. Знаете ли вы, что вы можете использовать Firebase в качестве промежуточного программного обеспечения для аутентификации, и вам больше не нужно будет хранить сеансы в своей базе данных? Сегодня я расскажу о написании промежуточного программного обеспечения для вашего экспресс-приложения с использованием только Firebase-admin. Вот шаги, необходимые для создания промежуточного программного обеспечения с Firebase.
Создайте учетную запись в Google. Если у вас нет учетной записи в Google, вы можете создать ее здесь. После создания учетной записи перейдите в консоль Google Firebase и создайте учетную запись, если у вас ее нет. После создания учетной записи вам нужно будет создать проект в Firebase. создание проекта даст вам объект конфигурации, который позволит вам подключить ваше приложение к базе данных, хранилищу и службам аутентификации Firebase. Firebase предоставляет вам сервисную учетную запись, которая позволяет использовать firebase-admin в вашем бэкэнде.
Установите Firebase-Admin в узле: установите firebase-admin в своем приложении узла, запустив npm install firebase-admin — save. Это сохранит Firebase Admin в зависимостях вашего приложения на случай, если вы захотите запустить его в другой среде.
Создайте объект конфигурации Firebase: создайте файл конфигурации firebase, который будет инициализировать ваш объект firebase-admin для использования в приложении. Это одноэлементный класс.
{
"type": "service_account",
"project_id": "<project-id>",
"private_key_id": "<private-key-id>",
"private_key": "<private-key>",
"client_email": "<client-email>",
"client_id": "<client-id>",
"auth_uri": "
"token_uri": "
"auth_provider_x509_cert_url": "
"client_x509_cert_url": "
}
Инициализируйте firebase для вашего приложения: после создания объекта конфигурации и запроса Firebase и его служб (база данных и аутентификация) вам нужно будет инициализировать Firebase в вашем приложении следующим образом:
require('dotenv').config();
import firebase from 'firebase-admin';
var serviceAccount = require('./firebase-service-account.json');
export default firebase.initializeApp({
credential: firebase.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DATABASE_URL
})
Создайте контроллер для аутентификации пользователей на серверной части, используя инициализированный файл конфигурации firebase. Это предполагает, что вы уже обработали аутентификацию пользователей в своем клиентском приложении. Вы можете проверить документы для аутентификации пользователей.
// import the firebase config into the auth controller
import firebase from '../../firebase';
const firebaseAuth = async (req, res) => {
try {
// req.body the payload coming from the client to authenticate the user
// uid is the firebase uid generated when a user is authenticated on the firebase client
const userRequest = await firebase.database().ref(`users/${req.body.uid}`).once('value');
const userPayload = userRequest.val();
if (userPayload) {
// create tokenClaims if you wish to add extra data to the generated user token
const tokenClaims = {
roleId: userPayload.roleId
}
// use firebase admin auth to set token claimsm which will be decoded for additional authentication
await firebase.auth().setCustomUserClaims(user.uid, tokenClaims);
return res.status(200).json({data: tokenClaims});
} else {
return res.status(404).json({error: {message: 'No user found'}});
}
} catch (error) {
return res.status(500).json({
error: { message: 'could not complete auth request'}
});
}
}
export default {
firebaseAuth
}
Создайте промежуточное программное обеспечение, которое проверяет токен firebase, отправленный из заголовка запроса, например так
// Import Firebase Admin initialized instance to middleware
import firebase from '../../firebase';
const roleRanks = {
superAdmin: 1,
admin: 2,
user: 3
};
export const decodeFirebaseIdToken = async (req, res, next) => {
if (!req.headers.id_token) {
return res.status(400).json({
error: {
message: 'You did not specify any idToken for this request'
}
});
}
try {
// Use firebase-admin auth to verify the token passed in from the client header.
// This is token is generated from the firebase client
// Decoding this token returns the userpayload and all the other token claims you added while creating the custom token
const userPayload = await firebase.auth().verifyIdToken(req.headers.id_token);
req.user = userPayload;
next();
} catch (error) {
return res.status(500).json({
error
});
}
};
// Checks if a user is authenticated from firebase admin
export const isAuthorized = async (req, res, next) => {
if (req.user) {
next();
} else {
return res.status(401).json({
error: {
message: 'You are not authorised to perform this action. SignUp/Login to continue'
}
});
}
};
// Checks if a user has the required permission from token claims stored in firebase admin for the user
export const hasAdminRole = async (req, res, next) => {
try {
const roleRequest = await firebase.database().ref('roles').once('value');
const rolesPayload = roleRequest.val();
const role = rolesPayload.find((role) => role.id === roleRanks.admin)
if (req.user.roleId <= role.id) {
next();
} else {
return res.status(403).json({
error: {
message: 'You are not permitted to access this resource'
}
});
}
} catch(error) {
return res.status(500).json({
error: {
message: 'An error occurred while getting user access. Please try again'
}
});
}
};
Используйте промежуточное ПО в маршруте. Наконец, после создания промежуточного ПО вы можете использовать это промежуточное ПО в маршруте и увидеть, как оно работает:
import {
hasAdminRole,
decodeFirebaseIdToken,
isAuthorized
} from '../controllers/middleware/auth.middleware';
const UserRoute = (router) => {
// Get all users
router.route('/users')
.get(
decodeFirebaseIdToken,
isAuthorized,
hasAdminRole,
UserController.getAllUsers
)
}
export default UserRoute;
Вот как все это объединяется в файле точки входа для вашего экспресс-приложения:
import express from 'express';
import path from 'path';
import bodyParser from 'body-parser';
import routes from './routes';
import cors from 'cors';
const app = express();
const router = express.Router();
const headers1 = 'Origin, X-Requested-With, Content-Type, Accept';
const headers2 = 'Authorization, Access-Control-Allow-Credentials, x-access-token';
const whitelist = [process.env.CLIENT_URL];
const corsOptionsDelegate = (req, callback) => {
let corsOptions;
if (whitelist.indexOf(req.header('Origin')) !== -1) {
corsOptions = { origin: true };
} else if (process.env.NODE_ENV === 'production') {
corsOptions = { origin: true };
} else {
corsOptions = { origin: false };
}
callback(null, corsOptions);
};
// setup body parser
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
// Use express backend routes
routes(router);
const clientHeaderOrigin = process.env.CLIENT_URL;
app.use(cors(corsOptionsDelegate));
app.use((req, res, next) => {
const origin = req.headers.origin;
if(whitelist.indexOf(origin) > -1){
res.header('Access-Control-Allow-Origin', origin);
} else {
res.header('Access-Control-Allow-Origin', clientHeaderOrigin);
}
res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, PATCH, OPTIONS, PUT');
res.header('Access-Control-Allow-Headers', `${headers1},${headers2}`);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
// Add API Routes
app.use('/api', router);
const port = process.env.PORT || 3000;
// start the app by using heroku port
app.listen(port, () => {
console.log('App started on port: ' + port);
});
Вам не нужно использовать другой пакет в качестве промежуточного программного обеспечения для аутентификации и хранить сеансы в базе данных.
Не забудьте задать мне любой вопрос или если есть какая-то часть, которую вы не понимаете.