Ghost blog uses a magic link to authenticate members who wants to login. It's an easy 5 step process where in essence:
- magic link is requested from the website
- opening the magic link requests a session
- session is created using the magic token
- server redirects to main site with set-cookie headers
- session cookie is stored in the browser
Let's figure out how this works under the hood
Concept 1: Magic links
When you try and login with your email via the popup (Ghost portal) it sends a server request POST "/members/api/send-magic-link/"
with your email as a payload, the server then generates a temporary short lived JWT token and sends it to your email as a "magic link".
Following files and methods are used to generate a magic link:
[library/members-api] [router] → sendMagicLink
[library/members-api] → sendEmailWithMagicLink
[library/magic-link] → sendMagicLink
[library/magic-link/JWTTokenProvider] → create
[server/services/members/config] → getSigninUrl
The magic link created here by default is {main-url}/members/{token}
which when opened in a browser triggers a GET request to /members/{token}
endpoint. This endpoint is responsible for authenticating a member and generating a session cookie.
Concept 2: Session cookie
Next, a session cookie is generated and set in the requesting browser.
To figure out how this is done, let's see which methods and related files are used to perform this feat in the Ghost server code.
The first method that triggers after the /members/{token}
hits is called createSessionFromMagicLink
which in turn triggers exchangeTokenForSession
which is the method responsible for creating session cookie which is abstracted in /library/memberSSR
repo.
Following methods are used:
[server/services/members/middleware] → createSessionFromMagicLink
[library/membersSSR] → exchangeTokenForSession
exchangeTokenForSession
find or creates a member using the email stored in the token itself. If no member for this email is found users.create({name, email, labels})
is used to create a new user where users
is a members repository helper method which deals with the SQL database.
The following methods are used in sequence:[library/membersSSR] → _getMemberDataFromToken
[library/membersApi] → getMemberDataFromMagicLinkToken
[library/magicLink] → getDataFromToken → [JWTProvider] → validate
[library/membersApi] → getMemberIdentityData
[library/membersApi] → [services] → [membersBread] → read via email
[library/membersApi] → [membersRepo] → get → findOne via email
After figuring out which member it is, it then sets a session cookie in the response header via a set-cookie header field, using:[membersSSR] → _setSessionCookie
If everything goes accordingly, createSessionFromMagicLink
then redirects the browser to set the cookies in the browser. Alas, you've granted thy authentication.
Every subsequent request to the server then use the session cookie to extract the member info using the membersSSR helper methods.
Concept 3 Identity tokens
The last thing to note before moving on is the identity token service that Ghost server API provides out of the box. This identity token service uses the session cookie and converts it into a JWT token.
[server/services/members/middleware] → getIdentityToken
[library/membersSSR] → getIdentityTokenForMemberFromSession
However, not sure why, but the Ghost server doesn't have an on demand session verification endpoint. Which can come handy if you want to identify a user session on let's say your personal backend service for your SaaS application.