logo

Database

Insecure session expiration time

Need

Limitation of the lifetime of authentication tokens and sessions

Context

• Usage of Rust 1.75+ for building application services

• Usage of the jsonwebtoken crate for issuing JSON Web Tokens

Description

1. Non compliant code

use jsonwebtoken::{encode, EncodingKey, Header};
use serde::Serialize;

#[derive(Serialize)]
struct Claims {
    sub: String,
    role: String,
    // No "exp" claim: the token never expires...

The `create_token` function below issues a JSON Web Token whose claims contain only the subject and the role. There is no `exp` claim, so the token never expires. Tokens leak through browser storage, logs, proxies, referrer headers and compromised devices. A token without expiration keeps working indefinitely after it leaks: it survives logouts and password changes, and the only way to invalidate it is to rotate the signing key, which logs out every user. The same weakness appears with cookies. An actix-web session cookie built with `max_age(Duration::days(30))` persists on disk for a month, so anyone who uses the same browser, or steals the cookie file, inherits the session long after the user has left.

2. Steps

• Include an `exp` claim in every token passed to `jsonwebtoken::encode`, and an `iat` claim to record when it was issued.

• Keep access tokens short-lived, from 5 to 15 minutes, and use revocable refresh tokens for longer sessions.

• Keep expiration validation enabled in `jsonwebtoken::Validation`, and add `exp` to the required claims with `set_required_spec_claims`.

• Do not set `max_age` or `expires` on session cookies; let them end with the browser session.

• Enforce an idle timeout and an absolute timeout for sessions on the server, and invalidate them at logout and after password changes.

3. Secure code example

use jsonwebtoken::{encode, get_current_timestamp, EncodingKey, Header};
use serde::Serialize;

const TOKEN_LIFETIME_SECS: u64 = 15 * 60;

#[derive(Serialize)]
struct Claims {
    sub: String,...

The corrected function adds the registered `iat` (issued at) and `exp` (expiration) claims. `get_current_timestamp` returns the current Unix time in seconds, and the token expires 15 minutes later. `jsonwebtoken::decode` validates `exp` by default, with a small leeway for clock differences, so an expired token is rejected with an `ExpiredSignature` error without extra code. A short lifetime bounds the damage of a leaked token to a few minutes. Long sessions should be built on top of short access tokens, with a refresh token that is stored server-side and can be revoked at logout. For cookie-based sessions, the equivalent fix is to omit `max_age` and `expires` so the cookie ends with the browser session, and to enforce an idle and an absolute timeout on the server.