Рубрики
Без рубрики

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

Это обновленное сообщение об использовании FireBase-admin в качестве промежуточного программного обеспечения в приложении узла

Автор оригинала: Victor Nwaiwu.

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

Скриншот 2019-03-27 в 1.32.17 PM.PNG

Установите FireBase-admin в узле: Установите FireBase-admin в приложении узла, запустив NPM Установите Firebase-admin – Сохранить. Это сохранит Admin FireBase в зависимостях приложений в случае, если вы хотите запустить его в другой среде. Создание объекта Config FireBase: создайте файл конфигурации FireBase, который инициализирует ваш объект FireBase-Admin, который будет использоваться в приложении. Это класс Singleton.

{
  "type": "service_account",
  "project_id": "",
  "private_key_id": "",
  "private_key": "",
  "client_email": "",
  "client_id": "",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/.iam.gserviceaccount.com"
}

Инициализируйте FireBase для вашего приложения: после создания объекта Config и требуя 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);
});

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