Kermit
Kermit

Reputation: 3407

Firebase Auth - Set session expiration

How can I set session expiration for a Firebase auth session? By default the session never expires.

I wish for the session to expire after 8 hours of inactivity.

I have read the documentation but cannot figure out how to set session expiration.

My code for signing in the user and performing tasks on sign in and sign out

firebase.auth().signInWithEmailAndPassword(data.email, data.password)

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    //Signed in
  }else{
    //Signed out
  }
}

Thanks for all replies! I have tried but cannot seem to get Firebase-admin to work.

Firebase-db.js

const admin = require('firebase-admin')

const databaseConnection = {
  serviceAccountFile: './serviceAccount.json',
  databaseURL: 'https://myProject.firebaseio.com/'
}

const serviceAccount = require(databaseConnection.serviceAccountFile)

const app = admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: databaseConnection.databaseURL
}, 'test')

const database = admin.database(app)

module.exports = database

sessionSignout.js

const functions = require('firebase-functions')   
const database = require('../../firebase-db')
const admin = database.admin

exports.sessionSignout = functions
.region('europe-west1')
.pubsub
.schedule('*/15 * * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => {
  database.ref(`users`)
  .once('value', (usersSnapshots) => {
    usersSnapshots.forEach((snapshot) => {
      const uid = snapshot.key
      admin.auth().revokeRefreshTokens(uid)
    })
  })
}

I get error

Error: function execution failed. Details: Cannot read property 'auth' of undefined

Upvotes: 5

Views: 5010

Answers (2)

Kermit
Kermit

Reputation: 3407

Thanks for all the answers! I just wanted to share my code for others to use.

I already had code in place to accommodate presence awareness.

index.js

import database from './firebase/firebase' //Firebase setup for client

firebase.auth().onAuthStateChanged((user) => {
  //Handle login and redirect

  if (user) {
    //We are logged in
    addPresenceAwarenessListener()
  }else{
    ...
  }
}

const addPresenceAwarenessListener = () => {
  // Create a reference to the special '.info/connected' path in 
  // Realtime Database. This path returns `true` when connected
  // and `false` when disconnected.
  database.ref('.info/connected').on('value', (snapshot) => {
    // If we're not currently connected, don't do anything.
    if (snapshot.val() == false) {
      return
    }

    const uid = firebase.auth().currentUser.uid

    //Push last login/logout to user profile
    const userLastLoginOutRef = database.ref(`users/${uid}`)
    userLastLoginOutRef.onDisconnect().update({lastLoginOut: firebase.database.ServerValue.TIMESTAMP})
    .then(() => { userLastLoginOutRef.update({lastLoginOut: firebase.database.ServerValue.TIMESTAMP}) })
  })
}

Session handling - expire sessions after n hours (setting "sessExp" in database)

firebase-db.js - Basic Firebase setup for cloud functions

const admin = require('firebase-admin')

const databaseConnection = {
  serviceAccountFile: './my-project.json',
  databaseURL: 'https://my-project.firebaseio.com/'
}

const serviceAccount = require(databaseConnection.serviceAccountFile)

const app = admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: databaseConnection.databaseURL
}, 'remindMiNotifier')

const database = admin.database(app)
module.exports = database

sessionSignout.js - Signout user after a specific time period - if specified. Default to eternal session.

const functions = require('firebase-functions')
const moment = require('moment')
const database = require('../../firebase-db')
const admin = database.app

//Import enviroment variable config (.env)
require('dotenv').config()

//Export cron job - deploy: firebase deploy --only functions:sessionSignout
exports.sessionSignout = functions
.region('europe-west1')
.pubsub
.schedule('*/15 * * * *')
.timeZone('Europe/Stockholm')
.onRun(async (event) => {
  //Start execution
  const now = moment()
  const defaultSessionTime = 0 //Eternal session

  //Get all users and calculate inactive time - time since last login
  let logoutUsersArray = []
  await database.ref(`users`)
  .once('value', (usersSnapshots) => {
    usersSnapshots.forEach((snapshot) => {
      const userData = snapshot.val()
      const lastLoginOut = (userData.lastLoginOut) ? userData.lastLoginOut : 0
      //Only process users that has a login/out time stamp
      if(lastLoginOut > 0){
        const userSessionTime = (userData.sessExp) ? userData.sessExp : defaultSessionTime
        const hoursSinceLastLoginOut = now.diff(lastLoginOut, 'hours')
        const logoutUser = ( userSessionTime > 0 && (hoursSinceLastLoginOut > userSessionTime) )

        if(logoutUser){
          const userId = snapshot.key
          const userName = (userData.alias) ? userData.alias : userData.displayName
          const email = (userData.email) ? userData.email : ''
          const userObject = {
            userId,
            userName,
            email,
            lastLoginOut,
            diffHours: now.diff(lastLoginOut, 'hours')
          }
          logoutUsersArray.push(userObject)
        }
      }
    })
  })
  console.log('logoutUsersArray', logoutUsersArray)

  //Collect all promises to carry out 
  let myPromises = []

  // Revoke all refresh tokens for each user
  logoutUsersArray.forEach((logoutUser) => {
    const uid = logoutUser.userId
    myPromises.push(
      admin.auth().revokeRefreshTokens(uid)
      .then(() => {
        return admin.auth().getUser(uid)
      })
      .then((userRecord) => {
        return new Date(userRecord.tokensValidAfterTime).getTime() / 1000
      })
      .then((timestamp) => {
        // Retrieve the timestamp of the revocation, in seconds since the epoch.
        console.log('Tokens revoked at: ', timestamp)
        return Promise.resolve(true)
      })
      .catch((err) => {
        console.error('Error', err)
        return Promise.reject(err)
      })
    )
  })

  //Execute promises
  console.log('Execute promises')
  return Promise.all(myPromises)
  .then(() => Promise.resolve(true))
  .catch((err) => {
    console.error('Error', err)
    return Promise.reject(err)
  })

})//End sessionSignout

Documentation on firebase-admin can be found here.

Upvotes: 2

Doug Stevenson
Doug Stevenson

Reputation: 317467

The documentation you linked says that you can use the Firebase Admin SDK to revoke a user's refresh tokens in order to terminate their session. This code must run on a backend you control, which means that you won't be able to do it in the client app. The backend will need to know when the user became "inactive", by whatever definition of that you choose. Wiring this all up is non-trivial, but possible.

Upvotes: 5

Related Questions