auth-js
auth-js copied to clipboard
feat: add `setSession` support for a SSR context
Adds support for calling setSession() in a server-side rendering context. When used in SSR, the access and refresh tokens are typically sent from the browser to the server. Both of them represent the user's session.
Users can thus call:
await client.setSession({
access_token: req.cookies['my-access-token'],
refresh_token: req.cookies['my-refresh-token']
})
To correctly extract the session information from the two cookies. The client can then be used to call other APIs and automatic refresh token management will take place.
Here's a Mermaid description of the issue:
sequenceDiagram
participant G as GoTrue
participant S as Server
participant B as Browser
participant C as Cookies
participant L as Local Storage
B-->>S: GET /login
S-->>B: 200 OK Render /login
B-->>G: POST /sign-in { email, password }
G-->>B: 303 See Other server/authenticate...
B-->>S: GET /authenticate
S-->>B: 200 OK Render /authenticate
activate B
B-->>B: getSessionFromURL
B-->>L: putItem(session, ...)
B-->>C: document.cookie = 'my-access-token...'
B-->>C: document.cookie = 'my-refresh-token...'
deactivate B
note over G, L: After 1 week the user comes back to /route on server -- no issues
activate B
B-->>S: GET /route
B-->>S: Cookie: my-access-token ...
B-->>S: Cookie: my-refresh-token ...
activate S
S-->>S: setSession(req.cookies['my-refresh-token'])
S-->>G: POST /verify?grant_type=refresh_token
G-->>S: 200 OK access token, refresh token
S-->>S: return session information to app
S-->>B: 200 OK prerendered content
deactivate S
B-->>B: _refreshSession
B-->>L: getItem(session)
L-->>B: session { access_token, refresh_token, expires_at }
B-->>G: POST /verify?grant_type=refresh_token
G-->>B: 200 OK access token, refresh token
B-->>L: putItem(session)
deactivate B
note over G, L: Immediately after login user navigates to /route -- issue after 1 hour
activate B
B-->>S: GET /route
B-->>S: Cookie: my-access-token ...
B-->>S: Cookie: my-refresh-token ...
activate S
S-->>S: setSession(req.cookies['my-refresh-token'])
S-->>G: POST /token?grant_type=refresh_token
G-->>S: 200 OK access token, refresh token
S-->>S: return session information to app
note over B, S: Start reuse timer!
S-->>B: 200 OK prerendered content
deactivate S
B-->>B: _refreshSession
B-->>L: getItem(session)
L-->>B: session { access_token, refresh_token, expires_at }
note over L, B: now is before expires_at!
note over L, B: No need to renew refresh token
note over G, L: 1 hour passes, app notices expires_at and tries to refresh token
B-->>G: POST /token?grant_type=refresh_token
G-->>B: 401 Unauthorized refresh token already used
note over G, B: 1 hour passed after the first validation of the refresh token!
deactivate B
note over G, L: With setSession({ access_token, refresh_token }) fix -- no-issue
activate B
B-->>S: GET /route
B-->>S: Cookie: my-access-token ...
B-->>S: Cookie: my-refresh-token ...
activate S
S-->>S: setSession({ refresh_token: req.cookies['my-refresh-token']), access_token: ... })
S-->>S: access token is not expired no post to GoTrue
S-->>S: return session information to app
note over B, S: Start reuse timer!
S-->>B: 200 OK prerendered content
deactivate S
B-->>B: _refreshSession
B-->>L: getItem(session)
L-->>B: session { access_token, refresh_token, expires_at }
note over L, B: now is before expires_at!
note over L, B: No need to renew refresh token
note over G, L: 1 hour passes, app notices expires_at and tries to refresh token
B-->>G: POST /token?grant_type=refresh_token
G-->>B: 200 OK access_token, refresh_token
note over G, B: 1 hour passed after the first validation of the refresh token!
B-->>L: putItem(session, ...)
B-->>C: document.cookie = 'my-access-token...'
B-->>C: document.cookie = 'my-refresh-token...'
note over L, G: Ok but what if the user closes the tab until 1 hour passes and then navigates to /route
B-->>S: GET /route
B-->>S: Cookie: my-access-token ...
B-->>S: Cookie: my-refresh-token ...
activate S
S-->>S: setSession({ refresh_token: req.cookies['my-refresh-token']), access_token: ... })
S-->>S: access token IS expired, refreshing in GoTrue
S-->>G: POST /token?grant_type=refresh_token
G-->>S: 200 OK access token, refresh token
S-->>S: return session information to app
note over B, S: Start reuse timer!
S-->>B: 200 OK prerendered content
deactivate S
B-->>B: _refreshSession
B-->>L: getItem(session)
L-->>B: session { access_token, refresh_token, expires_at }
B-->>B: now is after expires_at!
B-->>G: POST /token?grant_type=refresh_token
note over B, G: this occurs milliseconds after the refresh from the server
note over B, G: thus the refresh_token although used is within a reuse period
G-->>B: 200 OK access_token, refresh_token
B-->>L: putItem(session, ...)
B-->>C: document.cookie = 'my-access-token...'
B-->>C: document.cookie = 'my-refresh-token...'
note over G, L: All is good in the world!
deactivate B
:tada: This PR is included in version 1.24.0 :tada:
The release is available on:
Your semantic-release bot :package::rocket:
:tada: This PR is included in version 2.0.0-rc.11 :tada:
The release is available on:
Your semantic-release bot :package::rocket: