Administra las cookies de sesión

Firebase Auth permite administrar las cookies de sesión del nivel del servidor en sitios web tradicionales basados en este tipo de cookies. Esta solución tiene muchas ventajas por sobre los tokens de ID de corta duración del lado del cliente, que en ocasiones requieren un mecanismo de redirección cada vez que necesitan actualizar la cookie de sesión cuando esta vence:

  • Seguridad mejorada mediante tokens de sesión basados en JWT que solo se pueden generar mediante cuentas de servicio autorizadas.
  • Cookies de sesión sin estado que contienen todos los beneficios de usar JWT para la autenticación. Esta cookie de sesión tiene las mismas reclamaciones (incluidas las personalizadas) que el token de ID, por lo que se aplican las mismas verificaciones de permisos.
  • Capacidad de crear cookies de sesión con fechas de vencimiento personalizadas que pueden ser desde 5 minutos hasta 2 semanas.
  • Flexibilidad para aplicar políticas de cookies basadas en los requisitos de las aplicaciones: los dominios, la ruta, la seguridad, httpOnly, entre otros.
  • Capacidad de revocar las cookies de sesión cuando se sospeche que un token robado usa la API de revocación del token de actualización.
  • Capacidad de detectar la revocación de sesión en los principales cambios en las cuentas.

Accede

Si suponemos que una aplicación usa cookies almacenadas en servidor httpOnly, ingresa un usuario en la página de acceso con el SDK cliente. Se genera un token de ID de Firebase y se envía a una terminal de acceso a la sesión mediante HTTP POST donde se genera una cookie de sesión mediante el SDK de Admin. Si se hace correctamente, el estado debería borrarse del almacenamiento del lado del cliente.

firebase.initializeApp({
  apiKey: 'AIza…',
  authDomain: '<PROJECT_ID>.firebasepp.com'
});

// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);

// When the user signs in with email and password.
firebase.auth().signInWithEmailAndPassword('user@example.com', 'password').then(user => {
  // Get the user's ID token as it is needed to exchange for a session cookie.
  return user.getIdToken().then(idToken = > {
    // Session login endpoint is queried and the session cookie is set.
    // CSRF protection should be taken into account.
    // ...
    const csrfToken = getCookie('csrfToken')
    return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
  });
}).then(() => {
  // A page redirect would suffice as the persistence is set to NONE.
  return firebase.auth().signOut();
}).then(() => {
  window.location.assign('/profile');
});

Para generar una cookie de sesión a cambio del token de ID proporcionado, se requiere una terminal HTTP. Envía el token a la terminal, lo que configurará un tiempo de duración personalizado para la sesión con el SDK de Firebase Admin. Se deben tomar las medidas adecuadas con el fin de prevenir ataques de falsificación de solicitudes entre sitios (CSRF).

Node.js

app.post('/sessionLogin', (req, res) => {
  // Get the ID token passed and the CSRF token.
  const idToken = req.body.idToken.toString();
  const csrfToken = req.body.csrfToken.toString();
  // Guard against CSRF attacks.
  if (csrfToken !== req.cookies.csrfToken) {
    res.status(401).send('UNAUTHORIZED REQUEST!');
    return;
  }
  // Set session expiration to 5 days.
  const expiresIn = 60 * 60 * 24 * 5 * 1000;
  // Create the session cookie. This will also verify the ID token in the process.
  // The session cookie will have the same claims as the ID token.
  // To only allow session cookie setting on recent sign-in, auth_time in ID token
  // can be checked to ensure user was recently signed in before creating a session cookie.
  admin.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.end(JSON.stringify({status: 'success'});
  }, error => {
    res.status(401).send('UNAUTHORIZED REQUEST!');
  });
});

Java

@POST
@Path("/sessionLogin")
@Consumes("application/json")
public Response createSessionCookie(LoginRequest request) {
  // Get the ID token sent by the client
  String idToken = request.getIdToken();
  // Set session expiration to 5 days.
  long expiresIn = TimeUnit.DAYS.toMillis(5);
  SessionCookieOptions options = SessionCookieOptions.builder()
      .setExpiresIn(expiresIn)
      .build();
  try {
    // Create the session cookie. This will also verify the ID token in the process.
    // The session cookie will have the same claims as the ID token.
    String sessionCookie = FirebaseAuth.getInstance().createSessionCookie(idToken, options);
    // Set cookie policy parameters as required.
    NewCookie cookie = new NewCookie("session", sessionCookie /* ... other parameters */);
    return Response.ok().cookie(cookie).build();
  } catch (FirebaseAuthException e) {
    return Response.status(Status.UNAUTHORIZED).entity("Failed to create a session cookie")
        .build();
  }
}

Python

@app.route('/sessionLogin', methods=['POST'])
def session_login():
    # Get the ID token sent by the client
    id_token = flask.request.json['idToken']
    # Set session expiration to 5 days.
    expires_in = datetime.timedelta(days=5)
    try:
        # Create the session cookie. This will also verify the ID token in the process.
        # The session cookie will have the same claims as the ID token.
        session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
        response = flask.jsonify({'status': 'success'})
        # Set cookie policy for session cookie.
        expires = datetime.datetime.now() + expires_in
        response.set_cookie(
            'session', session_cookie, expires=expires, httponly=True, secure=True)
        return response
    except auth.AuthError:
        return flask.abort(401, 'Failed to create a session cookie')

En aplicaciones confidenciales, se debe verificar el auth_time antes de crear la cookie de sesión para reducir el riesgo de sufrir un ataque en caso de que se robe un token de ID:

Node.js

admin.auth().verifyIdToken(idToken).then((decodedIdTokens) => {
  // Only process if the user just signed in in the last 5 minutes.
  if (new Date().getTime() / 1000 - decodedIdToken.auth_time < 5 * 60) {
    // Create session cookie and set it.
    return admin.auth().createSessionCookie(idToken, {expiresIn})...
  }
  // A user that was not recently signed in is trying to set a session cookie.
  // To guard against ID token theft, require re-authentication.
  res.status(401).send('Recent sign in required!');
});

Java

// To ensure that cookies are set only on recently signed in users, check auth_time in
// ID token before creating a cookie.
FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
long authTimeMillis = TimeUnit.SECONDS.toMillis(
    (long) decodedToken.getClaims().get("auth_time"));

// Only process if the user signed in within the last 5 minutes.
if (System.currentTimeMillis() - authTimeMillis < TimeUnit.MINUTES.toMillis(5)) {
  long expiresIn = TimeUnit.DAYS.toMillis(5);
  SessionCookieOptions options = SessionCookieOptions.builder()
      .setExpiresIn(expiresIn)
      .build();
  String sessionCookie = FirebaseAuth.getInstance().createSessionCookie(idToken, options);
  // Set cookie policy parameters as required.
  NewCookie cookie = new NewCookie("session", sessionCookie);
  return Response.ok().cookie(cookie).build();
}
// User did not sign in recently. To guard against ID token theft, require
// re-authentication.
return Response.status(Status.UNAUTHORIZED).entity("Recent sign in required").build();

Python

# To ensure that cookies are set only on recently signed in users, check auth_time in
# ID token before creating a cookie.
try:
    decoded_claims = auth.verify_id_token(id_token)
    # Only process if the user signed in within the last 5 minutes.
    if time.time() - decoded_claims['auth_time'] < 5 * 60:
        expires_in = datetime.timedelta(days=5)
        expires = datetime.datetime.now() + expires_in
        session_cookie = auth.create_session_cookie(id_token, expires_in=expires_in)
        response = flask.jsonify({'status': 'success'})
        response.set_cookie(
            'session', session_cookie, expires=expires, httponly=True, secure=True)
        return response
    # User did not sign in recently. To guard against ID token theft, require
    # re-authentication.
    return flask.abort(401, 'Recent sign in required')
except ValueError:
    return flask.abort(401, 'Invalid ID token')
except auth.AuthError:
    return flask.abort(401, 'Failed to create a session cookie')

Tras acceder, todas las secciones del sitio web que tengan protección contra el acceso deben comprobar la cookie de sesión y verificarla antes de ofrecer contenido restringido según las reglas de seguridad.

Node.js

// Whenever a user is accessing restricted content that requires authentication.
app.post('/profile', (req, res) => {
  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.
  admin.auth().verifySessionCookie(
    sessionCookie, true /** checkRevoked */).then((decodedClaims) => {
    serveContentForUser('/profile', req, res, decodedClaims);
  }).catch(error => {
    // Session cookie is unavailable or invalid. Force user to login.
    res.redirect('/login');
  });
});

Java

@POST
@Path("/profile")
public Response verifySessionCookie(@CookieParam("session") Cookie cookie) {
  String sessionCookie = cookie.getValue();
  try {
    // 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.
    final boolean checkRevoked = true;
    FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(
        sessionCookie, checkRevoked);
    return serveContentForUser(decodedToken);
  } catch (FirebaseAuthException e) {
    // Session cookie is unavailable, invalid or revoked. Force user to login.
    return Response.temporaryRedirect(URI.create("/login")).build();
  }
}

Python

@app.route('/profile', methods=['POST'])
def access_restricted_content():
    session_cookie = flask.request.cookies.get('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.
    try:
        decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
        return serve_content_for_user(decoded_claims)
    except ValueError:
        # Session cookie is unavailable or invalid. Force user to login.
        return flask.redirect('/login')
    except auth.AuthError:
        # Session revoked. Force user to login.
        return flask.redirect('/login')

Verifica las cookies de sesiones con la API verifySessionCookie del SDK de Admin. Esta es una operación con sobrecarga baja. Inicialmente, los certificados públicos se consultan y almacenan en caché hasta que vencen. La verificación de cookies de sesión se puede realizar con los certificados públicos de sesión sin ninguna solicitud de red adicional.

Si la cookie no es válida, asegúrate de que se borren y que el usuario vuelva a acceder. Hay una opción adicional disponible para verificar la revocación de sesiones. Ten en cuenta que esto agrega una solicitud de red adicional cada vez que se verifica una cookie de sesión.

Por motivos de seguridad, las cookies de sesión de Firebase no se pueden usar con otros servicios de Firebase debido a su período de duración personalizado, que puede ser de hasta 2 semanas. Se espera que todas las aplicaciones que usan cookies almacenadas en el servidor realicen verificaciones de permisos tras usarse.

Node.js

admin.auth().verifySessionCookie(sessionCookie, true).then((decodedClaims) => {
  // Check custom claims to confirm user is an admin.
  if (decodedClaims.admin === true) {
    return serveContentForAdmin('/admin', req, res, decodedClaims);
  }
  res.status(401).send('UNAUTHORIZED REQUEST!');
}).catch(error => {
  // Session cookie is unavailable or invalid. Force user to login.
  res.redirect('/login');
});

Java

try {
  final boolean checkRevoked = true;
  FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(
      sessionCookie, checkRevoked);
  if (Boolean.TRUE.equals(decodedToken.getClaims().get("admin"))) {
    return serveContentForAdmin(decodedToken);
  }
  return Response.status(Status.UNAUTHORIZED).entity("Insufficient permissions").build();
} catch (FirebaseAuthException e) {
  // Session cookie is unavailable, invalid or revoked. Force user to login.
  return Response.temporaryRedirect(URI.create("/login")).build();
}

Python

try:
    decoded_claims = auth.verify_session_cookie(session_cookie, check_revoked=True)
    # Check custom claims to confirm user is an admin.
    if decoded_claims.get('admin') is True:
        return serve_content_for_admin(decoded_claims)
    else:
        return flask.abort(401, 'Insufficient permissions')
except ValueError:
    # Session cookie is unavailable or invalid. Force user to login.
    return flask.redirect('/login')
except auth.AuthError:
    # Session revoked. Force user to login.
    return flask.redirect('/login')

Salir

Cuando un usuario sale del cliente, esto se puede controlar en el lado del servidor mediante una terminal. Una solicitud POST/GET debería hacer que se borre la cookie de sesión. Ten en cuenta que la cookie permanecerá activa hasta su fecha de vencimiento incluso si se borra.

Node.js

app.post('/sessionLogout', (req, res) => {
  res.clearCookie('session');
  res.redirect('/login');
});

Java

@POST
@Path("/sessionLogout")
public Response clearSessionCookie(@CookieParam("session") Cookie cookie) {
  final int maxAge = 0;
  NewCookie newCookie = new NewCookie(cookie, null, maxAge, true);
  return Response.temporaryRedirect(URI.create("/login")).cookie(newCookie).build();
}

Python

@app.route('/sessionLogout', methods=['POST'])
def session_logout():
    response = flask.make_response(flask.redirect('/login'))
    response.set_cookie('session', expires=0)
    return response

La API de revocación cierra todas las sesiones del usuario, por lo que no queda otra solución que volver a acceder. En las aplicaciones confidenciales se recomienda establecer una duración menor.

Node.js

app.post('/sessionLogout', (req, res) => {
  const sessionCookie = req.cookies.session || '';
  res.clearCookie('session');
  admin.auth().verifySessionCookie(sessionCookie).then((decodedClaims) => {
    return admin.auth().revokeRefreshTokens(decodedClaims.sub);
  }).then(() => {
    res.redirect('/login');
  }).catch((error) => {
    res.redirect('/login');
  });
});

Java

@POST
@Path("/sessionLogout")
public Response clearSessionCookieAndRevoke(@CookieParam("session") Cookie cookie) {
  String sessionCookie = cookie.getValue();
  try {
    FirebaseToken decodedToken = FirebaseAuth.getInstance().verifySessionCookie(sessionCookie);
    FirebaseAuth.getInstance().revokeRefreshTokens(decodedToken.getUid());
    final int maxAge = 0;
    NewCookie newCookie = new NewCookie(cookie, null, maxAge, true);
    return Response.temporaryRedirect(URI.create("/login")).cookie(newCookie).build();
  } catch (FirebaseAuthException e) {
    return Response.temporaryRedirect(URI.create("/login")).build();
  }
}

Python

@app.route('/sessionLogout', methods=['POST'])
def session_logout():
    session_cookie = flask.request.cookies.get('session')
    try:
        decoded_claims = auth.verify_session_cookie(session_cookie)
        auth.revoke_refresh_tokens(decoded_claims['sub'])
        response = flask.make_response(flask.redirect('/login'))
        response.set_cookie('session', expires=0)
        return response
    except ValueError:
        return flask.redirect('/login')

Verifica las cookies de sesión con una biblioteca de JWT de terceros

Si tu backend está en un lenguaje que no es compatible con el SDK de Firebase Admin, de todas formas podrás verificar las cookies de sesión. En primer lugar, busca una biblioteca JWT de terceros para tu lenguaje. A continuación, verifica el encabezado, la carga útil y la firma de la cookie de sesión.

Verifica que el encabezado de la cookie de sesión cumpla con las siguientes restricciones:

Reclamaciones del encabezado de la cookie de sesión de Firebase
alg Algoritmo "RS256"
kid ID de clave Debe corresponder a una de las claves públicas que aparecen en https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys

Verifica que la carga útil de la cookie de sesión cumpla con las siguientes restricciones:

Reclamaciones de la carga útil de la cookie de sesión de Firebase
exp Hora de vencimiento Debe ser en el futuro. El tiempo se mide en segundos transcurridos desde el punto de inicio del tiempo UNIX. La hora de vencimiento se establece en función de la duración personalizada que se proporciona cuando se crea la cookie.
iat Hora de emisión Debe ser en el pasado. El tiempo se mide en segundos transcurridos desde el punto de inicio del tiempo UNIX.
aud Público Debe ser el ID de tu proyecto de Firebase, el identificador único de tu proyecto de Firebase, que se encuentra en la URL de la consola de ese proyecto.
iss Emisor Debe ser "https://session.firebase.google.com/<projectId>"", en que <projectId> es el mismo ID de proyecto que se usó para aud anteriormente.
sub Asunto Debe ser una string que no esté vacía y debe ser el uid del usuario o el dispositivo.
auth_time Fecha de autenticación Debe ser en el pasado. La hora en que se autenticó el usuario. Coincide con el auth_time del token de ID que se usó para crear la cookie de sesión.

Por último, asegúrate de que la cookie de sesión tenga la firma de la clave privada correspondiente a la reclamación kid del token. Obtén la clave pública de https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys y usa una biblioteca JWT para verificar la firma. Usa el valor de max-age en el encabezado Cache-Control de la respuesta proveniente de esa terminal para determinar cuándo actualizar las claves públicas.

Si se ejecutan correctamente todas las verificaciones anteriores, puedes usar el asunto (sub) de la cookie de sesión como el ID del usuario o dispositivo correspondiente.

Enviar comentarios sobre...

Si necesitas ayuda, visita nuestra página de asistencia.