What this is about
After the login, the BFF has three tokens from Keycloak. The question is: where are they stored? The answer at CodamAI: in an encrypted cookie that only the BFF can open. The BFF makes the calls to CDMS and CIAS, not the browser.
Where each token lives
flowchart LR
B["Browser<br/>session cookie (encrypted)"] -- "cookie on every request" --> F["BFF<br/>decrypts:<br/>access, refresh, ID token"]
F -- "Authorization: Bearer <access token>" --> A["CDMS / CIAS"]
F -- "refresh token, ID token" --> K["Keycloak"]
| Token | Where | Who uses it |
|---|---|---|
| Access token | in the session cookie. The page can request it via /api/auth/session | the BFF on every API call |
| Refresh token | only in the session cookie | the BFF, to get a new access token |
| ID token | only in the session cookie | the BFF when signing out |
Why not simply store it in the browser, for example in localStorage? Because every script on the page can read from there. A single injected script could then steal the refresh token and use it to get new tokens for hours. An httpOnly cookie is invisible to scripts.
The session cookie
| Property | Value | Meaning |
|---|---|---|
httpOnly | yes | no JavaScript can read the cookie |
sameSite | lax | the browser does not send it with forms or script requests from foreign sites |
secure | yes under https | only over encrypted connections. The name then gets the prefix __Secure- |
path | / | applies to the whole application |
| Content | encrypted (JWE) | tokens, expiry time, an error field. The key is the portal’s secret (NUXT_AUTH_SECRET) |
| Size | split when larger than about 4 KB | the parts are named …session-token.0, .1 and so on |
In addition, the BFF sets short-lived cookies during the sign-in for state, PKCE and the return target. They are valid for at most 15 minutes.
JWE stands for JSON Web Encryption: the content is not only signed, but encrypted.
Lifetime: one hour, sliding
The session is valid for one hour. The hour is extended as long as the page is open. The user interface requests /api/auth/session every minute and when you return to the tab. Every request pushes the end back. At the same time, the BFF also renews the access token when it expires soon, see Renew the token.
| Tab open, page asks regularly | last request older than 1 h | refresh at Keycloak possible | What happens |
|---|---|---|---|
| yes | no | yes | Session continues, tokens are renewed |
| – | yes | – | Cookie expired, new login on the next page view |
| yes | no | no | Session gets an error, the user interface starts a new login |
Own prefix per portal
Cookies belong to a host name, not to a port. When several portals run on the same host, for example locally on localhost:3000 and localhost:3001, they see each other’s cookies. If all had the same cookie name, each portal would overwrite the other’s cookie, and each one has a different key:
-
1Browser→FrontendLogin in the hub sets
next-auth.session-token, encrypted with the hub's key -
2Browser→FrontendLogin in the CDMS portal overwrites the same cookie with its own key
-
3FrontendThe hub can no longer decrypt the cookieSession error, new login, which in turn signs out the other portal
That is why the CIAS portal uses its own prefix cias-auth. Its cookies are named cias-auth.session-token, cias-auth.csrf-token and so on. The hub and the CDMS portal use the default names next-auth.*.
How the BFF calls the API
-
1Browser→BFF
GET /api/kunden, the session cookie is sent automatically -
2BFFdecrypts the cookie. Is the access token missing, or is there an error in the session?then 401 to the browser, the user interface starts a new login
-
3BFF→CDMS
POST /api/rest/crm/customer/querywithAuthorization: Bearer <access token> -
4CDMS→BFFResponse
-
5BFF→Browserpasses the data on
The portals do not send the tenant and user headers for a tenant switch or user switch. See Tenant switch by header.