Necesito un inicio de sesión de Google para una aplicación de Firebase, para hacer esto utilicé varias fuentes:
Al intentar iniciar sesión en una cuenta de Google en producción, se produce el siguiente error al ejecutar la función de autenticación de código a continuación:
admin .auth() .verifySessionCookie(sessionCookie, true /** checkRevoked */) .then((decodedClaims) => { log("Decode success"); // inbetween checks log("Successfully decoded and authenticated"); next(); }) .catch((error) => { log("Error authenticating"); < ---- THIS IS THE PROBLEM ... });Este error SOLO ocurre cuando está en producción, es decir, se implementa en Firebase. Cuando se prueba localmente usando emuladores de Firebase que emulan el alojamiento y las funciones solamente (auth, firestore, base de datos, etc. son todos de producción), el inicio de sesión es exitoso. Al implementar, el inicio de sesión falla con el siguiente error.
Error:
Error al decodificar la cookie de sesión de Firebase. Asegúrese de pasar la cadena completa JWT que representa una cookie de sesión. Consulte https://firebase.google.com/docs/auth/admin/manage-cookies para obtener detalles sobre cómo recuperar una cookie de sesión.
A continuación se muestra una descripción general de alto nivel de los pasos/acciones realizadas
Descripción general de los pasos de las acciones realizadas
1. Visit any page eg /login 2. Click sign in with Google, execute the popup provider (see [here][3]) 2. 1. Sign in with Google account 2. Send token to firebase functions for verification ie `POST /sessionLogin` 3. Receive response (assume 200 OK) 4. Redirect to authenticated URLEl error está en el último paso, es decir, 4
Este error ocurre después de crear correctamente una sesión con el código de muestra /sessionLogin que se encuentra aquí en el sitio de firebase:
const auth = admin.auth(); auth.verifyIdToken(idToken).then(value => { debug("Token verified") return auth.createSessionCookie(idToken, {expiresIn}) .then((sessionCookie) => { // Set cookie policy for session cookie. const options = {maxAge: expiresIn, httpOnly: true, secure: true}; res.cookie('session', sessionCookie, options); // res.json(JSON.stringify({status: 'success'})); res.status(200).send("OK"); }).catch((error) => { debug(error); res.status(401).send('UNAUTHORIZED REQUEST!'); }); }).catch(reason => { debug("Unable to verify token"); debug(reason); res.status(401).send('INVALID TOKEN!'); }); Los registros responden con un Token verified y se envía un estado 200 al cliente.
Luego, el cliente redirige a una URL autenticada /user/dashboard que realiza la verificación de autenticación (ver a continuación) que falla y redirige de nuevo a /login :
const authenticate = (req, res, next) => { log("Authenticating"); // source: https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions const sessionCookie = req.cookies.session || ''; // Verify the session cookie. In this case an additional check is added to detect // if the user's Firebase session was revoked, user deleted/disabled, etc. return admin .auth() .verifySessionCookie(sessionCookie, true /** checkRevoked */) .then((decodedClaims) => { log("Decode success"); // inbetween checks log("Successfully decoded and authenticated"); next(); }) .catch((error) => { log("Error authenticating"); if(error.errorInfo && error.errorInfo.code && error.errorInfo.code === "auth/argument-error") { debug(error.errorInfo.message); res.redirect('/user/login'); return; } debug(error); // Session cookie is unavailable or invalid. Force user to login. req.flash("message", [{ status: false, message: "Invalid session, please login again!" }]) res.redirect('/user/login'); }); };que es middleware para la aplicación express:
admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: "https://my-company-default-rtdb.firebaseio.com", storageBucket: "gs://my-company.appspot.com" }); const app = express(); app.use(cors({origin: true})); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); app.use(morgan('dev')); app.use(cookieParser('0000-0000-0000-0000-0000')) app.set('trust proxy', 1) // trust first proxy // Attach CSRF token on each request. app.use(attachCsrfToken('/', 'csrfToken', (Math.random()* 100000000000000000).toString())); app.use(session({ secret: '0000-0000-0000-0000-0000', resave: false, name: '__session', store: new FirebaseStore({ database: admin.database() }), })); app.use(flash()); app.use(authenticate); // routes exports.app = functions.https.onRequest(app);1:12:02.796 Se inició la ejecución de la función de la aplicación PM
1:12:02.910 Autenticación de la aplicación PM
1:12:02.910 PM app Intentando verificar la cocción de la sesión
1:12:02.910 PM aplicación Cookies: {}
1:12:02.911 PM aplicación Error al autenticar
1:47:41.905 PM error de argumento/autenticación de la aplicación
1:12:02.911 PM [aplicación] Falló la descodificación de la cookie de sesión de Firebase. Asegúrese de pasar la cadena completa JWT que representa una cookie de sesión. Consulte https://firebase.google.com/docs/auth/admin/manage-cookies para obtener detalles sobre cómo recuperar una cookie de sesión.
1:12:02.937 PM [aplicación] La ejecución de la función tomó 141 ms, terminó con el código de estado: 302
Actualizar
Llame al backend para la autenticación:
const postIdTokenToSessionLogin = (idToken, csrfToken) => { return axios({ url: "/user/sessionLogin", method: "POST", data: { idToken: idToken, csrfToken: csrfToken, }, }).then(value => { console.log(value); if(value.status === 200) { window.location.assign("/user/dashboard"); } }).catch(reason => { console.error(reason); alert("Failed to login"); }); }Llamada del lado del cliente:
var provider = new firebase.auth.GoogleAuthProvider(); firebase.auth() .signInWithPopup(provider) .then(async value => { firebase.auth().currentUser.getIdToken().then(idToken => { // const idToken = value.credential.idToken; const csrfToken = getCookie('_csrf'); return postIdTokenToSessionLogin(idToken, csrfToken); }).catch(reason => { console.error("Failed to get current user token"); alert(reason.message); }); })/*.then(value => { window.location.assign("/user/dashboard") })*/.catch((error) => { console.error("Failed to sign in with Google"); alert(error.message); });Actualización 2:
Se actualizó la solicitud de axios del lado del cliente con lo siguiente, también se agregó el registro adicional de req.cookies
return axios({ url: "/user/sessionLogin", method: "POST", withCredentials: true, data: { idToken: idToken, csrfToken: csrfToken, }, })Registro adicional:
4:43:23.493 Aplicación PM Se inició la ejecución de la función
4:43:23.501 Autenticación de la aplicación PM
4:43:23.501 PM app Creando sesión
4:43:23.502 PM app /sessionLogin Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJ..."}
4:43:23.503 Token de aplicación PM verificado
4:43:23.503 aplicación PM {"nombre":redactado ,"imagen":"","iss":"","aud":"",...}
4:43:23.503 PM aplicación ==============
4:43:23.503 PM app /sessionLogin#verifyIdToken Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJ..."}
4:43:23.634 PM app /sessionLogin#createSessionCookie Cookies: {"csrfToken":"19888568527706150","session":"eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3N..."}
4:43:23.634 PM aplicación Cookie:
4:43:23.634 PM aplicación "eyJhbGciOiJSUzI1NiIsImtpZCI6InRCME0yQSJ9.eyJpc3MiOiJodHRwczovL3Nlc3Npb24uZmlyZWJhc2UuZ29vZ..."
4:43:23.634 aplicación PM ==============
4:43:23.643 PM aplicación [0mPOST /user/sessionLogin [32m200[0m 139.036 ms - 2[0m
4:43:23.643 PM aplicación La ejecución de la función tomó 150 ms, finalizó con el código de estado: 200
4:43:24.131 Aplicación PM Se inició la ejecución de la función
4:43:24.153 Autenticación de la aplicación PM
4:43:24.153 Aplicación PM Intentando verificar la cocción de la sesión
4:43:24.153 PM aplicación Cookies: {}
Actualización 3
Reescribe el acceso habilitado de API y NodeJS exactamente como se muestra en firebase.json :
{ "database": { "rules": "database.rules.json" }, "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "hosting": { "site": "my-company-admin-portal", "public": "public", "rewrites": [ { "source": "/api/**", "function": "api" }, { "source": "**", "function": "app" } ], "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] }, "storage": { "rules": "storage.rules" }, "emulators": { "auth": { "port": 9099 }, "functions": { "port": 5001 }, "database": { "port": 9000 }, "hosting": { "port": 5000 }, "storage": { "port": 9199 }, "ui": { "enabled": true } } }Le sugiero que revise esta publicación en la que tienen un problema similar.
En resumen, si revisa el repositorio quickstart-nodejs , debe agregar cookie-parser y body-parser . Aquí está el ejemplo que proporcionan en la publicación :
const admin = require("firebase-admin"); const functions = require("firebase-functions"); const next = require("next"); const cors = require("cors"); const express = require("express"); const cookieParser = require("cookie-parser"); const bodyParser = require("body-parser"); const dev = process.env.NODE_ENV !== "production"; const app = next({ dev, conf: { distDir: "next" } }); const handle = app.getRequestHandler(); admin.initializeApp({ credential: admin.credential.cert("Service account key"), databaseURL: "Path to database" }); const server = express(); server.use(cors({ origin: true })); server.use(bodyParser.json()); // Support URL-encoded bodies. server.use(bodyParser.urlencoded({ extended: true })); // Support cookie manipulation. server.use(cookieParser()); // Attach CSRF token on each request. server.use(attachCsrfToken('/', 'csrfToken', (Math.random()* 100000000000000000).toString())); function attachCsrfToken(url, cookie, value) { return function(req, res, next) { if (req.url === url) { res.cookie(cookie, value); } next(); } } server.post("/login", (req, res) => { if (req.body && req.body.idToken) { const idToken = `${req.body.idToken}`; const expiresIn = 60 * 60 * 24 * 5 * 1000; admin.auth().createSessionCookie(idToken, { expiresIn }).then((sessionCookie) => { const options = { maxAge: expiresIn, httpOnly: true, secure: true }; res.cookie("session", sessionCookie, options); res.end(JSON.stringify({ sessionCookie })); }, error => { res.status(401).send(error); }); } else { res.status(401).send("Token empty"); } }); server.post("/profile", (req, res) => { if (req.cookies && req.cookies.session) { const sessionCookie = `${req.cookies.session}`; admin.auth().verifySessionCookie( sessionCookie, true /** checkRevoked */).then((decodedClaims) => { res.end(JSON.stringify({ decodedClaims })); }).catch(error => { res.status(401).send(error); }); } else { res.status(401).send("Session empty"); } }); exports.next = functions.https.onRequest((req, res) => { if (req.method === "POST") { if (!req.path) req.url = `/${request.url}`; return server(req, res); } return app.prepare().then(() => handle(req, res)); });sessionCookie no está definido como en el código proporcionado en la pregunta.
// Authenticate middleware right now const authenticate = (req, res, next) => { log("Authenticating"); // No sessionCookie declared return admin .auth() .verifySessionCookie(sessionCookie, true /** checkRevoked */) // undefined passed here ^^^ } Debe pasar la cookie que configuró después de usar createSessionCookie en el método de verifySessionCookie como se muestra a continuación:
// updated authenticate middleware const authenticate = async (req, res, next) => { try { log("Authenticating"); // Read the value of cookie here const sessionCookie = req.cookies.session // Return unauthorized error if cookie is absent if (!sessionCookie) return res.sendStatus(401) const decodedClaims = await admin.auth().verifySessionCookie(sessionCookie, true) // cookie verified, continue } catch (e) { console.log(e) return res.send("An error occurred") } }