Membership access/details on other webapps

So I’ve been curious and tinkering with Ghost to use as a website and membership management software for a historical society I am volunteering with and will likely self-manage hosting. There are several things that I would need Ghost to be able to achieve for it to best fit my vision for our website such that it is self managed for those who are paying vs not (and currently paid or not).

  1. Further details for members. Storage of mailing addresses in the membership profile would be necessary to send to our printers for our physical publication for paid members. This would have to be available to export and send a list to our publisher. Of course, we have the emails for members for emails built in already. Is this possible, or have I overlooked this? I’ve spent only a short amount of time exploring the application installed on my dev build.

  2. Membership logins available to other webapps. I saw some past discussion regarding SSO/OAuth. Is this something that has been developed in the recent builds or something in the near future? I would like to integrate and have a single sign on and paid member exclusive access to several things: Discounts on a webstore, forums, and our archival database (CollectiveAccess software) with different access tiers for paid/non-paid members. Is something like this integrated already (SSO/OAuth) or something that I would have to develop in?

1 Like
  1. Ghost membership doesn’t have custom field functionality, they just implemented tags. You’ll have to connect to something that’s more like a CRM or an email sender like ConvertKit. You can sync with Zapier or through webhooks.

  2. I have not found any readily available auth mechanism that pairs well with Ghost, so you’ll probably have to do custom dev. I talked to someone who was struggling with this, but then used Netlify Identity (though Netlify being a static host, bring a whole host of other issues)

I can’t comment on Question #1, but for #2, SSO is possible for both staff and members with some coding. I have it working using OAuth2 with Google, Microsoft/Azure and GitHub as Identity Providers.

2 Likes

Oooh, interesting. Any chance you can share that code?

2 Likes

Apparently it has to be a “token swap mechanism”…whatever that is…you’re welcome.

Staff SSO can be done via SSO Adapter which is already built into Ghost code. The adapter can be deployed externally into‘content’ folder, hence would not be impacted by Ghost upgrades (I am
assuming Ghost upgrades do not touch content folder) A default one (which does nothing) is already provided under core. Although I should add only request object is provided to handler, so SSO must be done without modifying the response object.

As for SSO for members, at this point it looks like the only way to do it is by going into core code and intercepting the request using Express middleware as there is no hook built (or I have not found one) Then performing SSO functions to figure out if someone is trying to access using an Identity Provider and setting the user to that is all you need. As this requires a bit of core code update, one should be careful to add it back after a Ghost upgrade (most functionality can be kept under content and imported from core code to limit this amount)

For both cases, I have OAuth2 working (doing regular OAuth2 dance with userinfo API call, have not tried OIDC) which I did for a product I am building.

3 Likes

To add to my previous post and to clarify a bit more :
There is already middleware that intercepts each request to ‘/ghost’ (which pretty much happens once since admin client is a rich Ember.js app) .
backendApp.use('/ghost', require('../../services/auth/session').createSessionFromToken, require('../admin')());

Then following the trail, you can see the execution flow is as follows:
getRequestCredentials → getIdentityFromCredentials → getUserForIdentity
Return of previous one is fed to the subsequent one as a parameter. Ghost code doing this is the @tryghost/mw-session-from-token package as below:

const token = await getTokenFromRequest(req);
            if (!token) {
                return next();
            }
            const email = await getLookupFromToken(token);
            if (!email) {
                return next();
            }
            const user = await findUserByLookup(email);
            if (!user) {
                return next();
            }
            await createSession(req, res, user);

Whatever you do in the previous two functions, the final function, getUserForIdentity, is what determines who the logged in user will be. As long as you return a user object with a correct id property from this function, whether via a direct return or return a Promise and then resolve, Ghost will set the user in the session to that:
My final function simply does this:

async getUserForIdentity(userEmail) {       
      let user=null;
        return new Promise(async (resolve, reject) => {
        try{
    if (userEmail != undefined)  {
      user= await UserModel.getByEmail(userEmail.toLowerCase());
    }
       if (user !=undefined) {
        resolve({id:user.id});
       }else resolve(null);
        }catch (err){
            reject(err);
        }
    });
    }

(…at the end, I guess I could just return the result of UserModel.getByEmail directly, but I have not tested that)

Once the request passes thru this middleware, user session is set and request is intercepted by ‘./admin’ (as you can see at the top) which recognizes the user, since all the API calls going forward is tied to this user session.

1 Like