Hello there!

Need Help? We are right here!

Support Icon
miniOrange Email Support
success

Thanks for your Enquiry. Our team will soon reach out to you.

If you don't hear from us within 24 hours, please feel free to send a follow-up email to info@xecurify.com

Search Results:

×

Ghost Single Sign-On SSO


Ghost JWT Single Sign-On (SSO) solution by miniOrange provides secure Single Sign-On access into Ghost application using a single set of login credentials. This is done using JSON Web Token (JWT) tokens and it can be easily integrated with Ghost built in any framework or language. You can enable social login for your users to get secure access to applications using any of their existing social providers such as Facebook, Twitter, Google, or LinkedIn.

With miniOrange Ghost SSO, you get:

  • Seamless user login experience.
  • Endless customizations to your login forms and pages.
  • Simplified customer or user on-boarding.


Connect with External Source of Users


miniOrange provides user authentication from various external sources, which can be Directories (like ADFS, Microsoft Active Directory, Azure AD, OpenLDAP, Google, AWS Cognito etc), Identity Providers (like Shibboleth, Ping, Okta, OneLogin, KeyCloak), Databases (like MySQL, Maria DB, PostgreSQL) and many more.



Follow the step-by-step guide given below for Ghost Single Sign-On (SSO):

1.Configure Ghost in miniOrange

  • Login to miniOrange Dashboard and click on Apps >> Add an Application.
  • Ghost Single Sign-On (SSO) add app

  • Click on Create App button in JWT box.
  • Ghost Single Sign On SSO add app

  • In the next step, search for your application from the list, if your application is not found. Search for External / JWT App and you can set up your Application.
  • Ghost SSO (Single Sign-On): external app

  • Enter the name of your application and the redirect URL to the page where the JWT token is verified and click on Save.
  • Ghost SSO (Single Sign-On): redirect url

  • Custom App Name: The name of your Ghost application.
  • Redirect-URL: https://<your_Ghost_domain>/members/?action=signin
  • Identity Source (optional): You need to select the user store or the external IDP where the user accounts will be stored
  • If you click on edit for your application you can see the Single Sign-On URL. You will need this URL in the following steps so save it accordingly
  • Ghost SSO (Single Sign-On): select edit

    Ghost SSO (Single Sign-On): endpoints

  • You can download the certificate by clicking on ‘certificate’ in the options of your application. You will need the certificate in the following steps.
  • Ghost SSO (Single Sign-On): select certificates


2.Configure SSO in Ghost

  • In \content\themes\casper\default.hbs file you can add a custom SSO button in the div class ‘gh-head-actions’
  • {{#if @site.members_enabled}} {{#unless @member}} <a class="gh-head-button" href= '<YOUR SSO URL>', //"https://loginxyz.xecurify.com/moas/broker/login/jwt/273903?client_id=vk rLa5jmY0kChu4a&redirect_uri=http://localhost:2368/members/?action=signin", >MiniOrange SSO</a> {{/if}}
  • In place of the link in href-tag, you can replace it with your SSO URL. You can get this SSO URL from step 7 of Configure your application in miniOrange.
  • In \node_modules\@tryghost\members-api\lib\MembersAPI.js add the following function. You need to add your x509 certificate that you can get from step 8 of configure your application in miniOrange.
  • const JWTBuilder = require("./jwt-connector/JWTBuilder"); const cert = "< PLACE YOUR CERTIFICATE STRING HERE >"; var verified = false; async function getMemberDataFromSSOToken(jwt) { if(jwt) { //Initialize the JWT var jwtBuilder = new JWTBuilder.default; jwtBuilder.parseJwt(jwt); // set the secret that was shared by your IDP jwtBuilder.setSecret(cert); try { //Compare the hashed jwt with the one received verified = jwtBuilder.verifyJwt(); } catch (error) { console.error(error); } if (verified) { // once you find the JWT is verified, you can go ahead and get the data from JWT let user = jwtBuilder.getPayload(); if (!user) { return null; } let email = user["email"]; let name = user["first_name"] + user["last_name"]; let labels = []; let newsletters = []; const member = await getMemberIdentityData(email); if(member) { await MemberLoginEvent.add({ member_id: member.id }); return member; } const newMember = await users.create({ name, email, labels, newsletters, }); await MemberLoginEvent.add({ member_id: newMember.id }); return getMemberIdentityData(email); } } return null; }
  • Make sure to add the function in return variables at the bottom of the page.
  • return { middleware, getMemberDataFromMagicLinkToken, getMemberDataFromSSOToken, getMemberIdentityToken, ... };
  • \node_modules\@tryghost\members-ssr\lib\MembersSSR.js add the following 2 functions
  • /** * @method _getMemberDataFromSSOToken * * @param {JWT} token * * @returns {Promise} member */ async _getMemberDataFromSSOToken(token) { const api = await this._getMembersApi(); return api.getMemberDataFromSSOToken(token); } /** * @method exchangeSSOTokenForSession * @param {Request} req * @param {Response} res * * @returns {Promise} The member the session was created for */ async exchangeSSOTokenForSession(req, res) { if (!req.url) { return Promise.reject(new BadRequestError({ message: 'Expected token param containing JWT' })); } const {query} = parseUrl(req.url, true); if (!query || !query.id_token) { return Promise.reject(new BadRequestError({ message: 'Expected token param containing JWT' })); } const token = Array.isArray(query.id_token) ? query.id_token[0] : query.id_token; const member = await this._getMemberDataFromSSOToken(token); // perform and store geoip lookup for members when they log in if (!member.geolocation) { try { await this._setMemberGeolocationFromIp(member.email, req.ip); } catch (err) { // no-op, we don't want to stop anything working due to // geolocation lookup failing debug(`Geolocation lookup failed: ${err.message}`); } } this._setSessionCookie(req, res, member.email); return member; }
  • /core/server/services/members/middleware.js add the following function
  • const createSessionFromSSOLink = async function (req, res, next) { if (!req.url.includes("id_token=")) { return next(); } // req.query is a plain object, copy it to a URLSearchParams object so we can call toString() const searchParams = new URLSearchParams(""); Object.keys(req.query).forEach((param) => { // don't copy the id_token param if (param !== "id_token") { searchParams.set(param, req.query[param]); } }); try { const member = await membersService.ssr.exchangeSSOTokenForSession(req,res); const subscriptions = (member && member.subscriptions) || []; const action = req.query.action; if ( action === "signup" || action === "signup-paid" || action === "subscribe" ) { let customRedirect = ""; const mostRecentActiveSubscription = subscriptions .sort((a, b) => { const aStartDate = new Date(a.start_date); const bStartDate = new Date(b.start_date); return bStartDate.valueOf() - aStartDate.valueOf(); }) .find((sub) => ["active", "trialing"].includes(sub.status)); if (mostRecentActiveSubscription) { customRedirect = mostRecentActiveSubscription.tier.welcome_page_url; } else { const freeTier = await models.Product.findOne({ type: "free" }); customRedirect = (freeTier && freeTier.get("welcome_page_url")) || ""; } if (customRedirect && customRedirect !== "/") { const baseUrl = urlUtils.getSiteUrl(); const ensureEndsWith = (string, endsWith) => string.endsWith(endsWith) ? string : string + endsWith; const removeLeadingSlash = (string) => string.replace(/^\//, ""); const redirectUrl = new URL( removeLeadingSlash(ensureEndsWith(customRedirect, "/")), ensureEndsWith(baseUrl, "/") ); return res.redirect(redirectUrl.href); } } // Do a standard 302 redirect to the homepage, with success=true searchParams.set("success", true); res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`); } catch (err) { logging.warn(err.message); // Do a standard 302 redirect to the homepage, with success=false searchParams.set("success", false); res.redirect(`${urlUtils.getSubdir()}/?${searchParams.toString()}`); } };
  • Make sure to add the function in module.exports variables at the bottom of the page.
  • module.exports = { loadMemberSession, createSessionFromMagicLink, createSessionFromSSOLink, ... };
  • /core/server/web/members/app.js add the following line above ‘membersApp.use(middleware.createSessionFromMagicLink);’ in line 27
  • membersApp.use(middleware.createSessionFromSSOLink);
  • Your application now supports SSO via the miniOrange userstore. You can also add 2FA, MFA or authentication via external IDP or userstore by adding the configuration in the miniOrange Admin Console. You can follow the documentation at https://developers.miniorange.com/docs/idp/

3. Test SSO Configuration

Test SSO login to your Ghost account with miniOrange IdP:

    Using IDP Initiated Login

    • Login to miniOrange IdP using your credentials.
    • Ghost Single Sign-On (SSO)

    • On the Dashboard, click on Ghost application which you have added, to verify SSO configuration.
    • Ghost Single Sign-On (SSO) verify configuration


    Not able to configure or test SSO?


    Contact us or email us at idpsupport@xecurify.com and we'll help you setting it up in no time.



4. Login Using Social Provider (Optional)

miniOrange provides user authentication from various external sources, which can be Directories (like ADFS, Microsoft Active Directory, Microsoft Entra ID, OpenLDAP, Google, AWS Cognito etc), Identity Providers (like Okta, Shibboleth, Ping, OneLogin, KeyCloak), Databases (like MySQL, Maria DB, PostgreSQL) and many more. You can configure your existing directory/user store or add users in miniOrange.




External References

Want To Schedule A Demo?

Request a Demo
  



Our Other Identity & Access Management Products