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, OpenLDAP, AWS etc), Identity Providers (like Microsoft Entra ID, Okta, AWS), and many more. You can configure your existing directory/user store or add users in miniOrange.
 
		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.
 
				
				- Click on Create App button in JWT box.
 
				
				- 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.
 
				
				- Enter the name of your application and the redirect URL to the page where the JWT token is verified and click on Save.
 
				
 
				- 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
 
				
				
 
				- You can download the certificate by clicking on ‘certificate’ in the options of your application. You will need the certificate in the following steps.
 
				
		
		           
            
            
    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:
 
		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.
	
	
	
		4.1. Configure Google Apps as OAuth 2.0 Provider in miniOrange.
	  						
			- Go to miniOrange Admin Console.
 
			- From the left navigation bar select Identity Providers >> Add Identity Provider.
 
			
			- To configure OAuth/OpenID, select OAuth/OpenID from the dropdown.
 
            
            - Select Google.
 
            
			- Enter the following values:
 
			
				
					
						
							| IdP Name | 
							Custom Provider | 
						
						
							| Display Name | 
							Choose appropriate Name | 
						
						
                           | OAuth Callback URL | 
                           URL where the IDP redirects users after successful authentication. | 
                        
						
							| Client ID | 
							From step 4.2 | 
						
						
							| Client secret | 
							From step 4.2 | 
						
						
							| Scope | 
							email | 
						
			    
		    
	    
		
	    - Click on Next for Advanced tab.
 
        - Grant Type: Select the OAuth grant type used to obtain the access token.
 
        - Domain Mapping: Specify the domain linked to this Identity Provider for login.
 
        
        - Click on Save.
 
	
	  
		
		4.2. Configuring miniOrange as Service Provider (SP) in Google Apps
		  
			- Go to https://console.developers.google.com/ and sign up/login.
 
			- Click on  Select Project to create a new Google Apps Project,you will see a popup with the list of all your projects.
 
			
			- You can click on the New project button to create new project.
 
			
			- Enter your Project name under the Project Name field and click on Create.
 
			
			- Go to Navigation Menu >> APIs >> Services >> Credentials.
 
			
			- Click on  Create Credentials button and then select OAuth Client ID from the options provided.
 
			
			- In case you are facing some warning saying that in order to create an OAuth Client ID, you must set a product name on consent screen (as shown in below image). Click on the Configure consent screen button.
 
			
			- Enter the required details such as App Name, User Support Email. and click on Save and Continue button.
 
			
			
			- Now for configuring scopes, click on Add or Remove the Scopes button.
 
			
			- Now, Select the Scopes to allow your project to access specific types of private user data from their Google Account and click on Save and Continue button.
 
			
			-  Go to the Credentials tab and click on Create Credentials button. Select Web Application from dropdown list to create new application. 
 
			
			- To get the Redirect URL:
 
			
				- Go to miniOrange Admin Console.
 
				- From the left navigation bar select Identity Provider.
 
				
		    - Copy the Callback URL as Redirect URL required for next step.
 
				
			
			- Enter the name you want for your Client ID under the name field and enter the Redirect/Callback URI and  click on Create button.
 
			
			- You will see a popup with the Client ID and Client Secret Copy your Client ID and Client Secret.
 
			
			- You have successfully completed your Google App OAuth Server side configurations.
 
		
  
  
  
	
	4.1. Configure LinkedIn as OAuth 2.0 Provider in miniOrange.
	
		- Go to miniOrange Admin Console.
 
		- From the left navigation bar select Identity Providers >> Add Identity Provider.
 
		
		- To configure OAuth/OpenID, select OAuth/OpenID from the dropdown.
 
		
        - Select LinkedIn.
 
        
		
		- Enter the following values:
 
			
				
					
						
							| IdP Name | 
							Custom Provider | 
						
						
							| Display Name | 
							Choose appropriate Name | 
						
						
                           | OAuth Callback URL | 
                           URL where the IDP redirects users after successful authentication. | 
                        
						
							| Client ID | 
							From step 4.2 | 
						
						
							| Client secret | 
							From step 4.2 | 
						
						
							| Scope | 
							email | 
						
			    
		    
	    
		
	    - Click on Next for Advanced tab.
 
        - Grant Type: Select the OAuth grant type used to obtain the access token.
 
        - Domain Mapping: Specify the domain linked to this Identity Provider for login.
 
        
        - Click on Save.
 
  
  
	4.2. Configure miniOrange as Service Provider (SP) in LinkedIn
	
		- Go to https://www.linkedin.com/secure/developer?newapp= in order to create a new application. Click on Create Application button to create a new application.
 
		
		- Enter the required details and click on the Create app button to save your changes.
 
		
		- Here you need to verify the provided page. If the verification of the page is not done, then SSO will not work.
 
		
		- Navigate to Products section and then select Sign in with LinkedIn.
 
		
		- Copy the Client Id and the Client Secret and save it in your miniOrange OAuth App configuration under Client Id and the Client Secret text fields respectively.
 
		
		- To get the Redirect URL:
 
		
			- Go to miniOrange Admin Console.
 
			- From the left navigation bar select Identity Provider.
 
			
      - Copy the Callback URL as Redirect URL required for next step.
 
			
		
		- Copy the Redirect/ Callback URl from above step and enter it under OAuth 2.0 -> Authorized Redirect URLs text box. Click on the Add button adjacent to the text box to save it. Finally, click on the Update button to save your configurations. (image on the next page).
 
		
	
	
	
	
	
	4.1. Configure Facebook as OAuth 2.0 Provider in miniOrange.
	
		- Go to miniOrange Admin Console.
 
		- From the left navigation bar select Identity Providers >> Add Identity Provider.
 
		
		- To configure OAuth/OpenID, select OAuth/OpenID from the dropdown.
 
        
        - Select Facebook.
 
        
		
		- Enter the following values:
 
			
				
					
						
							| IdP Name | 
							Custom Provider | 
						
						
							| Display Name | 
							Choose appropriate Name | 
						
						
                           | OAuth Callback URL | 
                           URL where the IDP redirects users after successful authentication. | 
                        
						
							| Client ID | 
							From step 4.2 | 
						
						
							| Client secret | 
							From step 4.2 | 
						
						
							| Scope | 
							email | 
						
			    
		    
	    
		
	    - Click on Next for Advanced tab.
 
        - Grant Type: Select the OAuth grant type used to obtain the access token.
 
        - Domain Mapping: Specify the domain linked to this Identity Provider for login.
 
        
        - Click on Save.
 
	
	
	4.2. Configure miniOrange as Service Provider (SP) in Facebook
	
		- First of all, Login/ Signup in Facebook developer console at https://developers.facebook.com/
 
		
		- Go to My apps.
 
		
		- Click on  Create app to create a new app.
 
		
		- Enter the required details and click on create app.
 
		
		- Complete the security check and click on Submit button.
 
		
		- Select  Facebook Login  by clicking on the set up button .
 
		
		- Select Facebook Login >> Settings under Products option in the navigation bar.
 
		
		- To get the Redirect URL:
 
		
			- Go to miniOrange Admin Console.
 
			- From the left navigation bar select Identity Provider.
 
			
			- Copy the Callback URL as Redirect URL required for next step.
 
			
		
		- Under Client OAuth Settings, enter the Callback/Redirect URL in the Valid OAuth Redirect URIs field. Click on the Save button to save your configurations.
Most importantly, your Callback/Redirect URI should be https and not http. It is mandatory while configuring Facebook as an OAuth Server. 
		
		- Go to Settings -> Basic to view your App Id and App Secret (Refer to the image below).
 
		
		- You have successfully completed your Facebook App OAuth Server side configurations.
 
	
	
	
	
	
	4.1. Configure Apple Apps as OAuth 2.0 Provider in miniOrange.
	
		- Go to miniOrange Admin Console.
 
		- From the left navigation bar select Identity Providers >> Add Identity Provider.
 
		
		- To configure OAuth/OpenID, select OAuth/OpenID from the dropdown.
 
        
        - Select Apple.
 
        
		
		- Fill in the details shown in the below image.
 
         
		  
		    
	        | Display Name: | 
	        Apple ID | 
	      
		   
	        | Client ID* | 
	         {Your service id} e.g. com.john.serviceid | 
	      
	      
	        | Client Secret* | 
	        eyJraWQiOiJCOTJQUDg1VUw4IiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJFW.... | 
	      
	      
	        | OAuth Authorize Endpoint | 
	        https://appleid.apple.com/auth/authorize?response_mode=form_post | 
	      
	      
	        | OAuth Access Token Endpoint*   | 
	        https://appleid.apple.com/auth/token | 
	      
		  
			| Scope | 
			name email | 
		
	     
		  
		
		
		
		 - Click on Next for Advanced tab.
 
            - Grant Type: Select the OAuth grant type used to obtain the access token.
 
            - Domain Mapping: Specify the domain linked to this Identity Provider for login.
 
            
                  
	        - Click on Save.
 
		
	4.2. Configure miniOrange as Service Provider (SP) in Apple Apps
	
		- Go to https://developer.apple.com click on Account and Login with your Apple developer account.
 
		
		- Click on Certificates, Identifiers and Profiles in Apple Account.
 
		
		- In the left menu Click on Identifiers and after that click on the Plus Icon(blue color). Click on Continue for the next 2 pages of Apple Account.
 
		
		- Enter Description and Bundle ID for the App ID. (The Bundle ID is best when it’s a reverse-dns style string.)
 
		
		- In the Capabilities section scroll down and select sign up with apple. Click on Edit and select Enable as a primary App Id and click on Save. Click on Continue and then click on Register.
 
		
		
		- In the right corner click on Continue and after that Register button.
 
		- Again click the Plus icon( blue color). Select Service IDs and click on Continue.
 
		
		- Enter Description and Identifier( Identifier would be your client id - Copy this id and Paste in the above field of Client ID). and click on the Continue and after that Register button.
 
		
		- Click Keys from the left menu.Click on the Plus icon to register a new key.
 
		
		- Give your Key a name, and select Sign In with Apple and click on Configure button
 
		
		- Select your Primary Id and click on the Save button and after that at the right corner click on Continue and Register button.
 
		
		- Click on the Download button once the key is downloaded click on the Done button.
 
		
		- In the left menu click on Identifiers.
 
		- At the right top click on App IDs.
 
		- Select Service IDs from the drop-down menu.
 
		
		- Select your Service Id from the List.
 
		- Select Sign In with Apple and click on Configure button.
 
		
		- Select the Primary App Id from the drop-down, and Enter the Domain and Redirect URL in Domains and Subdomains and Return URLs respectively and click on the Next button (You will get the Domain name and Redirect URL from miniOrange dashboard).
 
		
		- Verify the details and click on the Done button. After that at the right corner click on the Continue button.
 
		- Copy the Identifier value and click on the Save button.
 
		
		- Enter the Identifier value in the App Id field and copy the downloaded key value in App secret field of the Apple Login instruction miniOrange plugin.
 
		Generating Secret Key
		- Download the Ruby installer from the following link https://rubyinstaller.org/downloads/  and then install it.
 
		- Search for the Start command prompt with ruby open the ruby command prompt and install the JWT gem by running the following command on the command line: gem install jwt.
 
		- Copy the below code in a file and save the file with the .rb extension. Keep this .rb file and downloaded .p8 file in the same folder.
 
		- Enter the name of downloaded file example= "key.P8", key_id , client_id, team_id in the code
 
		  require 'jwt'
key_file = 'key.P8'
team_id = ''
client_id = ''
key_id = ''
ecdsa_key = OpenSSL::PKey::EC.new IO.read key_file
headers = {
'kid' => key_id
}
claims = {
'iss' => team_id,
'iat' => Time.now.to_i,
'exp' => Time.now.to_i + 86400*180,
'aud' => 'https://appleid.apple.com',
'sub' => client_id,
}
token = JWT.encode claims, ecdsa_key, 'ES256', headers
puts token
		  
		 
		- Open Ruby command prompt and run the above code using the following command: ruby filename.rb.
 
		- You will be provided with your Client secret key. Copy and save the secret key; you will need it to configure Apple ID in miniOrange Dashboard.
 
		
	
  
	
 
		 External References