diff --git a/docs/deployment/breaking-changes.md b/docs/deployment/breaking-changes.md
index 14124dba..d299b9f3 100644
--- a/docs/deployment/breaking-changes.md
+++ b/docs/deployment/breaking-changes.md
@@ -9,12 +9,13 @@ Please follow the migration guides if you need to upgrade your platform.
This table regroups all the breaking changes introduced, with the corresponding version in which the change was
implemented.
-| Change | Deprecated in | Changed in |
-|:------------------------------------------------------------|:--------------|:-----------|
-| [OpenCTI / OpenAEV compatibility](#octi-oaev-compatibility) | - | 2.2.0 |
-| [OpenAEV encryption of secret](#openaev-encryption) | - | 2.1.0 |
-| [OpenAEV renaming](#openaev-renaming) | 1.18.20 | 2.0.0 |
-| [OpenAEV CSRF](#openaev-csrf) | - | 2.3.4 |
+| Change | Deprecated in | Changed in |
+|:--------------------------------------------------------------|:--------------|:-----------|
+| [OpenCTI / OpenAEV compatibility](#octi-oaev-compatibility) | - | 2.2.0 |
+| [OpenAEV encryption of secret](#openaev-encryption) | - | 2.1.0 |
+| [OpenAEV renaming](#openaev-renaming) | 1.18.20 | 2.0.0 |
+| [OpenAEV CSRF](#openaev-csrf) | - | 2.3.4 |
+| [URL access token enforcement](#url-access-token-enforcement) | - | 2.5.0 |
## OpenAEV 2.2.0
@@ -86,4 +87,18 @@ For more details, see [this migration guide](breaking-changes/2.0.0-openaev-rena
Starting with **OpenAEV 2.3.4**, frontend-initiated API calls must include a valid CSRF token.
To prevent API authentication and connection issues, make sure all ecosystem components are upgraded to versions compatible with OpenAEV 2.3.4.
-For more details, see [this migration guide](breaking-changes/2.3.4-csrf-token-enforcement.md)
\ No newline at end of file
+For more details, see [this migration guide](breaking-changes/2.3.4-csrf-token-enforcement.md)
+
+## OpenAEV 2.5.0
+
+### Introduction
+
+
+
+#### URL access token enforcement for email links
+
+Starting with **OpenAEV 2.5.0**, OpenAEV no longer accepts legacy email links based on `userId` and `user` query parameters for player access flows.
+
+OpenAEV now requires token-based links using `GET /api/url/access?token=`, followed by a secure cookie and redirect flow.
+
+For more details, see [this migration guide](breaking-changes/2.5.0-url-access-token-enforcement.md)
\ No newline at end of file
diff --git a/docs/deployment/breaking-changes/2.5.0-url-access-token-enforcement.md b/docs/deployment/breaking-changes/2.5.0-url-access-token-enforcement.md
new file mode 100644
index 00000000..60609f45
--- /dev/null
+++ b/docs/deployment/breaking-changes/2.5.0-url-access-token-enforcement.md
@@ -0,0 +1,46 @@
+# URL access token enforcement for email links
+
+!!! info ""
+
+ * **Introduced in**: `OpenAEV 2.5.0`
+
+## Description of changes
+
+Starting with **OpenAEV 2.5.0**, OpenAEV no longer accepts legacy email URLs that rely on `userId` and `user` query parameters for player access flows.
+
+OpenAEV now requires a short-lived URL access token through:
+
+```text
+/api/url/access?token=
+```
+
+After the first successful access, OpenAEV sets a secure cookie and redirects the user to the target resource URL.
+
+## Impact
+
+Legacy links generated before this change are not compatible with OpenAEV 2.5.0.
+
+Typical symptoms include:
+
+- `401 Unauthorized` when opening old email links
+- Access failures on player routes that previously relied on legacy query parameters
+
+## Migration guide
+
+1. Upgrade OpenAEV to a version that supports URL access token links.
+2. Regenerate and resend player emails (Media Pressure, Challenge, and Lessons Learned) so users receive token-based links.
+3. Verify your `openaev.base-url` value is correct, because OpenAEV uses it to build email links.
+4. Configure token behavior with:
+ - `openaev.url.access.token.expiry-margin-days`
+ - `openaev.url.access.token.retention-days`
+
+!!! warning
+ Do not keep old links in operational runbooks. Ask users to open newly generated emails after upgrade.
+
+## Validation checklist after upgrade
+
+1. Send a new player email and confirm the URL format is `/api/url/access?token=...`.
+2. Open the link and verify the redirect succeeds.
+3. Confirm the platform sets a secure cookie (`HttpOnly`, `Secure`, `SameSite=Strict`).
+
+
diff --git a/docs/deployment/configuration.md b/docs/deployment/configuration.md
index 9b0c68de..e2377fb3 100644
--- a/docs/deployment/configuration.md
+++ b/docs/deployment/configuration.md
@@ -26,22 +26,24 @@ Here are the configuration keys, for both containers (environment variables) and
#### Basic parameters
-| Parameter | Environment variable | Default value | Description |
-|:-----------------------------------|:-----------------------------------|:----------------------|:-------------------------------------------------------------------------------------------------------------------------|
-| server.address | SERVER_ADDRESS | 0.0.0.0 | Listen address of the application |
-| server.port | SERVER_PORT | 8080 | Listen port of the application |
-| openaev.base-url | OPENAEV_BASE-URL | http://localhost:8080 | Base URL of the application, will be used in some email links |
-| server.servlet.session.timeout | SERVER_SERVLET_SESSION_TIMEOUT | 60m | Default duration of session (60 minutes) |
-| openaev.cookie-secure | OPENAEV_COOKIE-SECURE | `false` | Turn on if the access is done in HTTPS |
-| openaev.cookie-duration | OPENAEV_COOKIE-DURATION | P1D | Cookie duration (default 1 day) |
-| openaev.admin.email | OPENAEV_ADMIN_EMAIL | admin@openaev.io | Default login email of the admin user |
-| openaev.admin.password | OPENAEV_ADMIN_PASSWORD | ChangeMe | Default password of the admin user |
-| openaev.admin.token | OPENAEV_ADMIN_TOKEN | ChangeMe | Default token (must be a valid UUIDv4) |
-| openaev.admin.encryption_key | OPENAEV_ADMIN_ENCRYPTION_KEY | ChangeMe | Encryption key used for encrypting sensitive data in database. Encryption key and salt are used to generate a 256bit encryption key for encrypting purpose. |
-| openaev.admin.encryption_salt | OPENAEV_ADMIN_ENCRYPTION_SALT | ChangeMe | Encryption salt used for encrypting sensitive data in database. Must be at least 8 bytes long. Encryption key and salt are used to generate a 256bit encryption key for encrypting purpose |
-| openaev.healthcheck.key | OPENAEV_HEALTHCHECK_KEY | ChangeMe | The key to use in the health check endpoint (/api/health) |
-| inject.execution.threshold.minutes | INJECT_EXECUTION_THRESHOLD_MINUTES | 10 | Inject execution threshold in minutes. If this time is exceeded, the inject will be moved to the MAYBE_PREVENTED status. |
-| openaev.starterpack.enabled | OPENAEV_STARTERPACK_ENABLED | true | StarterPack feature, providing default endpoint, asset group, scenarios and dashboards |
+| Parameter | Environment variable | Default value | Description |
+|:--------------------------------------------|:--------------------------------------------|:----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| server.address | SERVER_ADDRESS | 0.0.0.0 | Listen address of the application |
+| server.port | SERVER_PORT | 8080 | Listen port of the application |
+| openaev.base-url | OPENAEV_BASE-URL | http://localhost:8080 | Base URL of the application, will be used in some email links |
+| server.servlet.session.timeout | SERVER_SERVLET_SESSION_TIMEOUT | 60m | Default duration of session (60 minutes) |
+| openaev.cookie-secure | OPENAEV_COOKIE-SECURE | `false` | Turn on if the access is done in HTTPS |
+| openaev.cookie-duration | OPENAEV_COOKIE-DURATION | P1D | Cookie duration (default 1 day) |
+| openaev.admin.email | OPENAEV_ADMIN_EMAIL | admin@openaev.io | Default login email of the admin user |
+| openaev.admin.password | OPENAEV_ADMIN_PASSWORD | ChangeMe | Default password of the admin user |
+| openaev.admin.token | OPENAEV_ADMIN_TOKEN | ChangeMe | Default token (must be a valid UUIDv4) |
+| openaev.admin.encryption_key | OPENAEV_ADMIN_ENCRYPTION_KEY | ChangeMe | Encryption key used for encrypting sensitive data in database. Encryption key and salt are used to generate a 256bit encryption key for encrypting purpose. |
+| openaev.admin.encryption_salt | OPENAEV_ADMIN_ENCRYPTION_SALT | ChangeMe | Encryption salt used for encrypting sensitive data in database. Must be at least 8 bytes long. Encryption key and salt are used to generate a 256bit encryption key for encrypting purpose |
+| openaev.healthcheck.key | OPENAEV_HEALTHCHECK_KEY | ChangeMe | The key to use in the health check endpoint (/api/health) |
+| inject.execution.threshold.minutes | INJECT_EXECUTION_THRESHOLD_MINUTES | 10 | Inject execution threshold in minutes. If this time is exceeded, the inject will be moved to the MAYBE_PREVENTED status. |
+| openaev.starterpack.enabled | OPENAEV_STARTERPACK_ENABLED | true | StarterPack feature, providing default endpoint, asset group, scenarios and dashboards |
+| openaev.url.access.token.expiry-margin-days | OPENAEV_URL_ACCESS_TOKEN_EXPIRY-MARGIN-DAYS | 7 | Number of days added after an exercise end date before URL access tokens expire |
+| openaev.url.access.token.retention-days | OPENAEV_URL_ACCESS_TOKEN_RETENTION-DAYS | 30 | Number of days to retain expired or revoked URL access tokens before the purge job deletes them |
#### Network and security
diff --git a/docs/deployment/url-access-token.md b/docs/deployment/url-access-token.md
new file mode 100644
index 00000000..207b7d93
--- /dev/null
+++ b/docs/deployment/url-access-token.md
@@ -0,0 +1,58 @@
+# URL access token
+
+This page explains how OpenAEV secures player email links with short-lived URL access tokens.
+
+## What is this?
+
+OpenAEV uses URL access tokens to protect links sent in player-facing emails.
+
+Instead of exposing persistent identifiers in query parameters, OpenAEV now generates an opaque token and sends links in this format:
+
+```text
+/api/url/access?token=
+```
+
+When a player opens the link, OpenAEV validates the token, sets a secure cookie, and redirects to the target page.
+
+## Why use it?
+
+This mechanism reduces the risk of data leakage in browser history, logs, and referrer headers.
+
+Main security benefits:
+
+- Token is short-lived and revocable.
+- Token is scoped to a user and an exercise.
+- OpenAEV stores only a SHA-256 hash in the database.
+- OpenAEV removes the token from the URL after first access.
+
+!!! tip
+ This change hardens security without changing the player experience.
+
+## How do I do it?
+
+### Enable and configure token behavior
+
+1. Set the token validity margin after the exercise end date.
+2. Set the retention window for purge operations.
+
+| Parameter | Environment variable | Default value | Description |
+|:--|:--|:--|:--|
+| `openaev.url.access.token.expiry-margin-days` | `OPENAEV_URL_ACCESS_TOKEN_EXPIRY-MARGIN-DAYS` | `7` | Adds a grace period after `exercise.end_date` to compute token expiration. |
+| `openaev.url.access.token.retention-days` | `OPENAEV_URL_ACCESS_TOKEN_RETENTION-DAYS` | `30` | Keeps revoked or expired tokens for audit before purge. |
+
+### Verify email link flow
+
+1. Send a Media Pressure, Challenge, or Lessons Learned email.
+2. Confirm the email contains `/api/url/access?token=...`.
+3. Open the link and verify OpenAEV returns a redirect to the target page.
+4. Verify the browser receives a cookie with `HttpOnly`, `Secure`, and `SameSite=Strict`.
+
+## Example
+
+A player receives this link:
+
+```text
+https:///api/url/access?token=
+```
+
+OpenAEV validates the token and redirects the player to the initial exercise resource.
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 8d75508e..e666e8bd 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -118,6 +118,7 @@ nav:
- Configuration: deployment/configuration.md
- Certificate validation: deployment/certificate-validation.md
- Authentication: deployment/authentication.md
+ - URL access token: deployment/url-access-token.md
- Upgrade: deployment/upgrade.md
- Ecosystem:
- Executors: deployment/ecosystem/executors.md
@@ -140,6 +141,8 @@ nav:
- OpenAEV renaming: deployment/breaking-changes/2.0.0-openaev-renaming.md
- OpenAEV encryption: deployment/breaking-changes/2.1.0-encrypting-password.md
- Scenario Generation from OpenCTI Security Coverage: deployment/breaking-changes/2.2.0-opencti-security-coverage.md
+ - CSRF token enforcement for frontend API calls: deployment/breaking-changes/2.3.4-csrf-token-enforcement.md
+ - URL access token enforcement for email links: deployment/breaking-changes/2.5.0-url-access-token-enforcement.md
- User Guide:
- Getting started: usage/getting-started.md
- Foundations: