(Обновлено) Использование Firebase-Admin в качестве промежуточного ПО для аутентификации в Express.js

Это обновленная версия этого поста, который я сделал в 2017 году. С тех пор firebase претерпела множество изменений и доработок. Это работает с 2019 года, и вы должны быть готовы к работе.
Вы, должно быть, слышали о простоте Firebase и о том, что это универсальное решение для управления базами данных, аутентификации и хранения. Знаете ли вы, что вы можете использовать Firebase в качестве промежуточного программного обеспечения для аутентификации, и вам больше не нужно будет хранить сеансы в своей базе данных? Сегодня я расскажу о написании промежуточного программного обеспечения для вашего экспресс-приложения с использованием только Firebase-admin. Вот шаги, необходимые для создания промежуточного программного обеспечения с Firebase.
Создайте учетную запись в Google. Если у вас нет учетной записи в Google, вы можете создать ее здесь. После создания учетной записи перейдите в консоль Google Firebase и создайте учетную запись, если у вас ее нет. После создания учетной записи вам нужно будет создать проект в Firebase. создание проекта даст вам объект конфигурации, который позволит вам подключить ваше приложение к базе данных, хранилищу и службам аутентификации Firebase. Firebase предоставляет вам сервисную учетную запись, которая позволяет использовать firebase-admin в вашем бэкэнде.

Скриншот 27 марта 2019 г., 13:32:17.png

Установите 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);
});

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

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *