auth-js icon indicating copy to clipboard operation
auth-js copied to clipboard

feat: add `setSession` support for a SSR context

Open hf opened this issue 3 years ago • 1 comments

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.

hf avatar Sep 21 '22 11:09 hf

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

hf avatar Sep 21 '22 17:09 hf

:tada: This PR is included in version 1.24.0 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Oct 10 '22 02:10 github-actions[bot]

:tada: This PR is included in version 2.0.0-rc.11 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

github-actions[bot] avatar Oct 11 '22 10:10 github-actions[bot]