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

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.

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.