I am creating a small app that to compliment the content on Ghost for my members and I would like a way for the app to only qualify members who are logged in via the Ghost site. To clarify, I would like to solely rely on Ghost’s authentication system for my app.
I did some scouting, it looks like the following cookies are stored for members. __cfduid, ghost-members-ssr, ghost-members-ssr.sig, ugid
Additionally, after exploring the database a bit I found the following in the settings table: members_public_key, members_private_key, members_email_auth_secret
I am also aware of possible relevant tables such as session and members. Could someone explain to me how they all tie together?
Here is my assumption of how it might work:
ghost-members-ssr.sig is the public key that I would use the members_private_key to decrypt via RSA, revealing the session info that I can use to check if the user is qualified i.e. ghost-members-ssr matches user email from backend and cookie has not expired. That said, there are some obvious gaps in my knowledge and I am unsure what the other cookie variables and members_email_auth_secret represent so I thought it would be best to check first before starting. Also, the decryption method is using RSA?
This is definitely doable but it’s gonna be a hack either way as these are not publicly documented or stable methods.
You can either get a JWT for a member and use that to authenticate into your application, but this won’t handle logout when the member logs out of Ghost. Or you can use the cookies like you say, but this will be a little more involved and require your app to be running on the same domain so that the cookies can be shared.
Thanks @egg, this answers so many of the questions I’ve had over the past week. We wanted to use Ghost’s authentication in order to benefit from its members system. Basically, have Ghost handle memberships for our app (that is on the same domain).
This kind of membership integration would make Ghost attractive to SaaS start-ups. Membership/subscriber and billing/payments available for their own service, and included in their blogging platform. A double win.
Those were very helpful indeed! I wanted to say I’ve implemented it but alas I can only say I’m nearly there (left with step 7 below)
Push JWT to my app, which validates it using members_public_key and returns the app cookie.
Cookie stores the email of the member, and has the same httpOnly, secure and sameSite settings.
App is located as a subdir via nginx proxy.
When user accesses the app, nginx checks if ghost generated cookie `ghost-members-ssr’ which stores the members email is the same as the apps generated cookie.
If ghost cookie expires, app won’t authenticate since it requires both ghost and app cookies.
With this setup, I’ve adopted a hybrid approach to ensure a smooth auth experience across ghost and the app while implicitly benefiting from security features set by Ghost.
Personally I hope that members account will gain the ability to add names or handles. After tinkering with this possibility, the potential to extend Ghost to create simple, integrated communities instead of relying on third party services (Disqus) or fully featured platforms (Discourse) is closer to reality than I thought!
I was able to get a JWT via API request, like you mentioned.
However, I haven’t been able to get the public key with which to validate the JWT. From searching on Github, I can see that members_public_key exists - but I’m struggling to see how I can retrieve it if I don’t have direct access to the database.
Ultimately, my goal is is to build a comments tool that leverages Ghost’s memberships. When a new customer signs up, I need an easy way to get the public key with which to validate JWTs from that user’s Ghost blog.
(For example, it would be great if the user could copy/paste the public key it into my site from a settings page in - say - their admin dashboard, or perhaps I could make an API request to their blog on a particular endpoint to fetch it.)
I’ve been poking around my own blog (which is hosted on Ghost Pro), but I haven’t found the public key anywhere.
The best I can see so far, is to fetch /members/api/member/ and then check the uuid on the backend via the /admin/members/ API. But that would only confirm that the UUID has a paid membership, not that the current logged-in user owns that uuid, making it exploitable. A JWT payload would be perfect for this.