Use Ghost members auth to log-in to custom app via cookies

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?

3 Likes

I’m also interested in learning about this.

Hey!

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.

External auth with JWT

You can make a GET request to /members/ssr in your theme which will give you a JWT and then send that to your application to validate (with members_public_key in the settings) and initiate a session. An example of a call to this endpoint is here: https://github.com/TryGhost/Ghost/blob/master/core/server/public/members.js#L75-L81

External auth with cookies

For this to work your app will have to be on the same domain as your Ghost site so they can share cookies.

You can take a look at the members-ssr package here: Members/packages/members-ssr at main · TryGhost/Members · GitHub and use the getMemberDataFromSession method. In order to use this you’ll have to pass in the required settings which you can get from the Ghost settings table. You’ll also have to pass in a getMembersApi function which returns a proxy to a members API object - the only method you’ll need is this one: Members/index.js at main · TryGhost/Members · GitHub

To implement this method you’ll need to use either the admin API or read from the db.

Hope this gives you a good start :relaxed:

10 Likes

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.

Or have I misunderstood how Ghost members works?

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)

  1. Custom JavaScript in theme to get a JWT via /members/ssr
  2. Push JWT to my app, which validates it using members_public_key and returns the app cookie.
  3. Cookie stores the email of the member, and has the same httpOnly, secure and sameSite settings.
  4. App is located as a subdir via nginx proxy.
  5. 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.
  6. On sign out, a custom JavaScript sends the members email to the app to remove the app cookie.
  7. 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!

1 Like

Thanks for the tips, Fabien!

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.

My goal

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.

Any tips on where to find it?

Thanks,
Dan


P.S. I opened this other forum thread with this same question.

Sorry to revive this old thread, but has /members/ssr been moved?

I’ve just tried loading it, both directly in Chrome and via JavaScript in the site footer whilst logged in, and it 404s.

I’m looking to get a JWT for logged-in members to send to my remote API, to authenticate them as paying members before returning premium data.

I can see the public key at /members/.well-known/jwks.json still, but not the actual JWT payload at /members/ssr with user data in it.

Thanks,

Rob

1 Like

Same experience that I am having.

—We have a custom API running along with Ghost and are successfully authenticating members.

We want to learn: How to authenticate against the admin cookie credentials?

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.

/members/ssr has been replaced in favour of /members/api/session

2 Likes

I’m looking to get a JWT for logged-in members to send to my remote API, to authenticate them as paying members before returning premium data.

Wrote up both JWT and Cookie solution here: Use Ghost authentication to login to custom app

4 Likes

Interesting summary, but you may want to fix the factual error in your first paragraph. Ghost does not do email/password authentication for members.

I see your blog isn’t hosted on Ghost - would you mind linking your site where you do use Ghost? I’m always interested to see where new users who are linking blog posts about Ghost are actually using it!

2 Likes