Welcome to our podcast, where we dive into everything Go High Levelβfrom mastering the basics to tackling the most complex tasks. I use GHL daily in my business and rely on Google NotebookLM to stay ahead of the curve, keeping up with all the latest GHL features, tools, and innovations. This podcast is powered by AI, fueled by the research and insights I personally curate to bring you the most valuable and up-to-date content.
Copy this link for a free trial of Go High Level - https://www.gohighlevel.com/highlevel-bootcamp?fp_ref=amplifi-technologies12
Imagine like spending your entire weekend upgrading your agency's security infrastructure. Oh man. The weekend warrior IT project. Right. You meticulously configure everything, you flip the switch on Sunday night, and you go to sleep feeling like an absolute tech genius. Naturally. But then you wake up Monday morning to just frantic messages because all 50 of your employees are permanently locked out of GoHighLevel. The whole business is totally paralyzed. Yeah. That is it's the ultimate structural bottleneck. Setting up enterprise level access. I mean, it looks fantastic on paper. But if you get that underlying architecture wrong, you're literally building a brick wall between your team and their workspace. Exactly. So, welcome to the deep dive. Today, we are going to make sure that nightmare scenario never happens to you. We have a lot of ground to cover to secure your agency, but uh before we get into the technical weeds, I want to give you something massive right up front. Always a good idea. If you are a digital marketing agency owner, check the show notes right now. We have a special offer waiting for you. A free 30-day GoHighLevel trial. That is double the standard trial length you usually see floating around out there. The link is sitting right below you in the show notes, so grab that now. Honestly, getting a full month to actually build and test the architecture we're about to break down, that's a huge advantage. It really is. So, let's establish our mission here. Today's deep dive is entirely focused on mastering access and security in GoHighLevel, which, uh, we'll just refer to as GHL moving forward. Specifically, we're looking at single sign on or SSO. Right. The big leagues. To unpack this, we've pulled together a really interesting stack of sources. We've got the official GHL support documentation, a setup guide from an agency consulting firm called Consult Evo, and to cap it all off, a highly technical developer blog post by Winston Brown on building custom pages SSO. We really need all three of those perspectives too. Implaining SSO fundamentally changes how your agency operates. I mean, it elevates your professionalism, sure, but as you tease out these documents, you quickly realize there are some heavily conflicting information out there. Oh, totally conflicting. Not to mention that massive gotcha we teased at the start, but let's set the baseline first. If I'm an agency owner, I already know what passwords are. I probably enforce a password manager for my team, right? Right, usually. So why am I going through the massive hassle of tearing out the standard login screen to install SSO? Well, because scaling a digital agency requires you to move past decentralized security. When you centralize your identity in an enterprise tool, something like Azure Active Directory, Auth, or Octo, you completely shift the burden of security off of GHL. Okay, let's unpack this. It's kind of like hiring a VIP bouncer for a private club. Instead of GHL checking your ID at the door, GHL just trusts the VIP bouncer, your identity provider, who already screened you. That's a great way to put it. GHL is a fantastic marketing platform, but you know, it is not a dedicated identity security tool. By outsourcing that heavy lifting, you can use Azure to enforce strict password rotation policies. Or like conditional multifactor authentication, right? Exactly. Say an employee tries to log into your GHL portal from a new country. Azure automatically demands an authenticator app code before it even lets GHL know the person is trying to access the system. Wow. And you gain total control over the user life cycle too. Think about offboarding. Offboarding is the best part. If an employee leaves under normal setup, you have to log into GHL, revoke their access. Then go to your email provider, revoke that. Then your project management software. Yeah, it's a whole checklist. It's a nightmare. With SSO, you revoke their access once inside your identity provider. Instantly, they're locked out of your entire digital ecosystem. Plus, reading through the docs, it allows the agency to completely hide the standard email and Google login options on the portal. You get a fully branded, white-labeled experience. Just your agency's custom login button. But, uh, we should have mentioned the prerequisites before anyone starts building this. Your agency has to be on the $497 a month GHL plan and you must have a white label domain configured. Right, you can't run this off the standard app.gohighlevel.com domain. No, because mechanically, the SSO routing relies on your custom domain to know exactly where to send the authentication tokens. Makes sense. Okay, so we meet the prerequisites, we want to centralize our security. Now we have to actually connect our identity provider to GHL and this this is where our sources gave me absolute whiplash. Ah, yes, you ran into the protocol maze. The protocol maze. It's wild. We have this setup guide from Consult Evo, and it meticulously walks the reader through setting up a SAML-based SSO integration. Which sounds very official. Right. It throws around terms like SAML ACS URLs and X.509 certificates, these complex XML documents used to establish trust between servers. It gives you this massive checklist. But then I turn to the official GoHighLevel documentation. What does it say? It states in bold highlighted text that GHL currently only supports OpenID Connect or OIDC. It explicitly says SAML is not supported yet. Yeah, this happens constantly in software development. Third party consultants, you know, they often publish guides based on early workarounds or beta features they hack together. While the platform officially moves in a totally different direction. Exactly. So, for anyone listening right now, throw out the SAML playbook, just toss it. You must use OpenID Connect. OIDC is native to modern web applications and it's the only protocol the official docs actually support today. Okay, so if we are entirely scrapping SAML and using OIDC, what's the actual mechanism? Like, what does the agency owner need to plug into GHL to make this handshake happen? Well, OIDC is a token-based authentication protocol. To introduce your identity provider to GHL, you basically need three pieces of data. Okay, what's the first one? First is the client ID, which acts as a public identifier for your app. Second is the secret, which is a highly secure, hidden string of characters that proves HighLevel is actually authorized to request data. And the third? The third is the OIDC configuration URL, sometimes called the Discovery URL. Yeah, I noticed the official docs heavily pushed that discovery URL over manually typing in endpoints. How does that actually work? It's super smart. Instead of manually typing in half a dozen different routing addresses like the authorization endpoint, the token endpoint, the user info endpoint, and inevitably making a typo that breaks the whole thing. Which I would definitely do. Right. We all would. The discovery URL acts like a digital business card. It usually ends in /.well-known/openid-configuration. You paste that one link into GHL, and GHL reads it to automatically map all the routing endpoints for you. It removes human error from the equation entirely. Exactly. Okay, that sounds dangerously easy. Just paste a digital business card link, copy a client ID and secret, and hit save. But here is the massive trap we talked about earlier. The Monday morning meltdown trap. Yes. When I read this in the official docs, I actually got frustrated. The documentation casually mentions, and I quote, "SSO in GHL is login only. It does not automatically create new users." Yep, there's the gotcha. Wait, so if I spend my weekend meticulously configuring Auth, and I hit enable on Sunday night, my team logs in on Monday morning, and they just get an error screen. Why wouldn't GHL just look at the validated email coming from my Auth account and automatically generate a profile on the spot? You would think it'd be that smart, right? Yeah. But building it that way creates a massive security vulnerability. Oh, yeah. And a billing nightmare, frankly. Really? How so? Think about it. If GHL assumed any incoming email with a matching domain should get a brand new account, it completely bypasses your agency's internal role assignment process. Oh, I see. Yeah, GHL demands that you explicitly tell it who's allowed inside the walls first, and what permissions they have before it accepts a login token for them. So, an unwary agency owner thinks they're rolling out a massive security upgrade, and instead, they've frozen their entire operations because nobody has a pre-existing profile that matches the new SSO parameters. Precisely. They basically built a wall and locked themselves out. So, how do we actually solve this? You have to pre-provision your users using the GHL API. Before you ever ask your team to use the new SSO login, you generate a private integration token inside GHL with the create or edit users scope. Okay, so you're pushing the list of approved people into GHL first. Right. You use that API to push your user list into the GHL database. But here's the mechanism you absolutely cannot mess up. You must map the external user ID parameter. The docs call this the remote ID. Yes, exactly. Every identity provider assigns a unique, permanent string of characters to a user. In OIDC, this is often called the sub claim. In Microsoft Azure, it's the OID or object ID. Okay, so a permanent random string. Right. When you fire that API call to create the user in GHL, you must inject that specific sub or OID string into the external user ID field. Okay, let me push back on that because it feels, I don't know, overly complicated. Why is that random string of characters so important? Why not just use their email address to match them up when they log in? Ah, because email addresses are fundamentally unstable identifiers. Unstable? People get married and change their names. Or your agency undergoes a rebrand and migrates to a new domain name. If you only map identities by email address and Jane Doe's email changes inside your Azure Active Directory. Oh, no. Yeah, the next time she logs into GHL via SSO, the system will look at the new email, say, "I don't have a Jane Doe with this email," and block her access. Ah, but if they are permanently tied together by that underlying, invisible remote ID string. Then the system becomes self-healing. That's brilliant. It really is. GHL receives the login token and looks for the remote ID first. It finds Jane's remote ID, lets her in, and then it notices that the email address coming from Azure is different from the old email it has on file. And it just updates it automatically. Automatically. It updates Jane's email address in its own database to match Azure. You never have to manually update user details in GHL again. That is a brilliant piece of architecture, but you absolutely have to know to set it up via the API first. So the API prevents the Monday meltdown by syncing the user data. But even if you prepopulate the database, HighLevel still doesn't trust you to just throw the gates open, do they? Not at all. They force you into a sandbox first. You literally cannot toggle SSO to on for your agency until an automated configuration test passes. It forces you to prove you know what you're doing. Basically. It mimics the exact flow of a user logging in. You click start test, it redirects you to your identity provider, you authenticate, and it sends the data token back to GHL. Okay, and what is GHL looking for in that test? For that test to pass, GHL verifies the scopes. Scopes are essentially the specific permissions like the exact pieces of data GHL is allowed to ask your identity provider to hand over. Right. The docs say the open and scope is mandatory, but they highly recommend including profile and email as well. Right, you need profile and email, so GHL can pull in the user's name and contact info. But the most vital requirement to pass this test, the absolute deal breaker, is a tiny little flag in the payload called email_verified. It must come back as true. Wait, why is that specific flag the deal breaker? Think about an exploit scenario. Someone spins up a rogue identity provider and registers an account using your CEO's email address. Okay, scary. But they never actually have to click a verification link in an inbox to prove they own that email. If GHL accepted that incoming token blindly, that bad actor could spoof the CEO's identity and walk right into your agency's dashboard with admin privileges. Oh, wow. Yeah, that would be catastrophic. Right. So, by strictly requiring the email verified equals true flag, GHL forces your identity provider to mathematically prove it has actually validated the human behind the keyboard. That makes perfect sense. Now, looking at the setup guide, there's a strict operational consequence to this testing phase. If you ever go back and edit your configuration, let's say you change a URL or add a new scope, it immediately invalidates your previous test, and GHL just turns SSO off by default. Yep. You have to retest the entire flow. Yeah. And if you delete the config, it resets everything and kicks everyone back to the standard email login. It is entirely designed to fail safely. If the trust relationship is altered, it shuts the door until you prove it works again. Exactly. And it actually creates a really smart rollout strategy for the agency, because GHL doesn't force you to hide the standard email and password login right away. Agency owners can use this to run a pilot program. Which I highly recommend doing. Right. You set up SSO, run the mandatory test, get it working for just yourself and a few senior managers. You let them use the SSO route for a week, while the rest of your 50 employees keep using their normal passwords. You iron out the kinks, verify the API is updating the remote IDs correctly. And only then do you flip the toggle to hide the standard login and make SSO mandatory for everyone. It is truly the only way you should be deploying this. Pilot first, mandate second. Okay, so we've locked down the front door of the agency. The external perimeter is totally secure. But our final source, the blog post by Winston Brown, takes us down an entirely different rabbit hole. Oh, yeah, the custom apps. Right. Because what if the agency owner is building custom marketplace apps that run inside of GHL. That's custom pages SSO. Yeah. And it requires a fundamentally different architectural approach. It's kind of like digital inception. You've successfully logged into GHL via Azure, but now you click on a custom app that loads in an iframe inside the GHL dashboard. Right. That internal app needs to know who you are. It needs to know if you're the agency owner or just a sub-account user without forcing you to log in a second time. Because the main SSO we just explored is about getting into the platform. Custom pages SSO is about securely passing the user's context from GHL down to a third-party application embedded within it. And according to Winston Brown's production guide, GHL solves this using an iframe post message system. Basically, your custom app yells up to the parent window, "Hey, GHL, who is currently looking at me?" And GHL yells back with an AES encrypted payload of data. Advanced Encryption Standard. GHL locks the user's role and location context in a digital vault and tosses it down to your iframe. Your back-end server decrypts it using a shared secret, and boom, your app knows exactly who the user is. The theory is beautiful. The execution though, as the blog post points out, is where developers slam into a brick wall. Yes. This is the technical trap that I found absolutely fascinating. Here's where it gets really interesting. If a developer follows the official GHL documentation, they see a simple, clean one-line decryption command using a JavaScript library called CryptoJS. It looks incredibly easy. It does. But Winston Brown was building his back-end in Python, using a framework called FastAPI. And when he ran standard Python AES decryption on the payload GHL sent, he got absolute garbage, padding errors, nonsense strings, it failed completely. Yeah, he had to do some serious cryptographic detective work to figure out why that was happening. What did he find? Well, it turns out that CryptoJS, the library GHL uses to encrypt the data before tossing it to the iframe, does not use standard modern key derivation when you feed it a simple string password. Okay, the way Winston explains this is wild. Think of standard AES encryption like finding a hidden decoder ring. Or like a digital safe that opens with a standard metal key. You provide the password, and the lock opens. Right, that's standard. But CryptoJS doesn't just use the key you give it. It melts that key down, mixes it with a random metal alloy called a salt, and hammers it out thousands of times before it finally fits the lock. That is a great way to visualize an OpenSSL legacy function called EVP_BytesToKey. Okay, say that again. EVP_BytesToKey. When GHL encrypts that user data, CryptoJS actually prepends the word "Salted" with two underscores and an 8-byte cryptographic salt to the very front of the file. Wait, it just sticks the word "Salted" in there? Literally. Then it derives the actual encryption key and the initialization vector by running a highly specific MD5 hashing loop. It hashes your password, the salt, and the previous hash over and over again. So, if you aren't using CryptoJS on your back-end, you are completely blind to this melting process. Completely blind. The official docs hide this entirely because CryptoJS handles that bizarre MD5 loop invisibly under the hood in that simple one-liner. But if you are coding your back-end in Python, or Go, or Ruby, and you just apply a standard AES 256 decryption algorithm. It fails every single time. Hmm. Because you haven't hammered out the right derived key. Wow. So, what did Winston actually have to do? He had to write a custom Python function from scratch just to replicate that legacy OpenSSL hashing loop before he could actually read the payload data. That's insane. It's the kind of technical hurdle that can cost a development team weeks of frustration if they don't know what to look for. I can imagine. So, assuming our listeners bypass the CryptoJS trap and successfully decrypt this payload, their app now knows who the user is. What are the production ready mandates Winston shares for keeping this custom app secure? Well, if we connect this to the bigger picture, he outlines several crucial rules. Number one. Never expose the shared secret client-side. The decryption must happen securely on your back-end server. Because if you decrypt in the browser, anyone can inspect the code and steal your secret key. Game over. Right, makes sense. Number two. When you receive that post message from GHL, you must validate the origin of the message. Wait, why do we have to validate the origin? Because any random malicious website could theoretically embed your application in an iframe and try to feed it fake context data via post message. Oh, to trick your app into thinking it's inside GHL. Exactly. You have to mathematically verify that the message actually came from the app.gohighlevel.com domain before you even attempt to decrypt it. Trust nothing on the internet. Always. Now, number three relates to session management. Once you've authenticated the user, you typically issue them a JSON Web Token, a JWT. Think of a JWT like a temporary digital wristband that proves who the user is as they navigate your app. Perfect analogy. Winston strongly recommends storing that JWT in the browser's session storage, not local storage. Okay, I know local storage persists even after you close the browser. Why is session storage better here? Because session storage is strictly scoped to that specific browser tab. When the user closes the iframe or the tab, the digital wristband dies with it. It perfectly matches the life cycle of the parent GHL session. Ah, I see. If you use local storage, a stale token might stay alive for days, which leads to his final mandate. Use short expiration times on those tokens. That makes total sense, because context changes rapidly inside GHL. Like, a user might switch from looking at the overarching agency view down to a specific client location view. Or an admin might update their permission role. Right. And if your custom app's token lives for a week, your app won't recognize that their permissions changed five minutes ago. Exactly. Short-lived tokens force your custom application to silently reauthenticate in the background via the iframe, pulling fresh context from GHL constantly. It ensures your app is never acting on outdated permissions. Man, it is incredibly satisfying to see the full architecture laid out like this. So, what does this all mean for the agency owner or developer listening right now? Let's recap the dual layers we've explored today. Let's do it. First, you have the main agency perimeter. By utilizing OpenID Connect with providers like Auth or Azure, you can shift the burden of complex security away from GHL, enforce organization-wide policies, and instantly manage access from one central hub. Just remember to use the API to map those external user IDs first. Or you are locking your team out on day one. Absolutely do not forget the API. And on the second layer, if you are extending GHL by building custom apps in iframes, you now have the road map to navigate the cryptography quirks of post message SSO. Right. You know how to replicate the OpenSSL key derivation to bypass the CryptoJS trap, validate your origins, and manage your token life cycle securely in session storage. You are building an absolute fortress, inside and out. But, uh, before we wrap up, exploring all of this raises a pretty provocative question. What's that? Well, we talked a lot about the massive benefits of centralizing your identity, putting all your eggs in the Auth or Azure Active Directory basket. It solves the weak password problem and streamlines onboarding. True. What happens to our digital ecosystems when that single central hub experiences a massive server outage? Yeah, it is the ultimate architectural tradeoff. Yeah. We are trading the scattered vulnerability of thousands of weak, reused passwords across dozens of random apps for the single point of failure of one master key. Right. If Azure goes down globally, you don't just lose access to your email. You lose access to GHL, your project management tools, your HR software. The entire business simply stops until Microsoft fixes the problem. It is a really sobering thought to end on. As our tools get more interconnected, the foundations they rest on become that much more critical. You are building a highly sophisticated security apparatus, but you are entirely dependent on the structural integrity of the provider you choose. It is a risk you have to carefully model against the undeniable security benefits. Which is exactly why you need time to build, test, and model this architecture for your own agency. So do not forget to claim the resource we mentioned at the start. 30 days is a long time to test. It's double the standard time. Your free 30-day GoHighLevel trial, giving you plenty of time to experiment with these exact OIDC and custom page configurations, is waiting for you right now. Click the link in the show notes, get in there, and start building your custom, white-labeled, fully secured agency infrastructure. You have the blueprint. Have fun building it. Thanks for joining us on the deep dive. We'll catch you on the next one.