{"id":887,"date":"2024-11-29T09:44:01","date_gmt":"2024-11-29T08:44:01","guid":{"rendered":"https:\/\/sparrow365.de\/?p=887"},"modified":"2024-11-29T09:45:46","modified_gmt":"2024-11-29T08:45:46","slug":"adding-entra-external-id-to-wordpress","status":"publish","type":"post","link":"https:\/\/sparrow365.de\/index.php\/en\/2024\/11\/29\/adding-entra-external-id-to-wordpress\/","title":{"rendered":"Adding Entra External ID to WordPress"},"content":{"rendered":"<p>After realising that I should really enable comments <a href=\"https:\/\/sparrow365.de\/index.php\/2024\/10\/28\/windows-365-und-azure-virtual-desktop-conditional-access-deep-dive\/\">in my last post<\/a>, I set out to do so in a way that fits my personal requirements. At the outset, the most obvious solution was adding <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/external-id\/external-identities-overview\">Entra External ID<\/a> Authentication and user management (<em>as one does<\/em> \ud83d\ude09).<\/p>\n<blockquote>\n<p><em>Entra External ID is a version of Entra ID (formerly Azure AD) designed for managing large collections of users who aren\u2019t associated with an organization\u2014typically customers. Hence the Industry Term &quot;Customer Identity and Access Management&quot; (CIAM).<\/em><\/p>\n<\/blockquote>\n<p>Now, one might think that since wordpress makes up 40% of the Internet, it obviously has a simple and robust commenting system by default. Which would be accurate for 99% of people, but I am a special little snowflake, and I take issue with several points:<\/p>\n<ol>\n<li>Allowing anonymous posting is out of the question, since I want to deal with spam even less than I want to hold user Information. For this you could enable and enforce user registration natively but,<\/li>\n<li>I don&#8217;t like the wordpress login page. Especially since I am a big fan of Multi-Factor Authentication, and the free options are not the nicest. I would much rather have an established identity Provider for this purpose<\/li>\n<li>Adding to this, the registration flow will always require an email address by default. Since I hope to run this blog for a while, I would slowly accumulate user information on my platform. I feel most uncomfortable with Email Addresses and would prefer free choice of usernames.<\/li>\n<\/ol>\n<p>Outside of the purely practical points, since I am an Entra ID Admin I was also looking for a (semi) reasonable chance to build up some experience working with Entra External ID. I have always found learning by doing to be the most effective.<\/p>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>Prerequisites<\/h2>\n<p>Now, I am no fan of testing in production, even in my personal projects. So the first thing I need is a temporary WordPress instance.<\/p>\n<p>As a starting point, I <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/app-service\/quickstart-wordpress\">spun up a WordPress Site on Azure App Service<\/a>. For my purposes, the free hosting plan more than sufficed, and the Azure deployment is pretty quick and painless.<\/p>\n<p>Once it was up and running, I installed my theme and a test page, just to make sure everything works as it does on my production site. Then the changes from my configuration: <\/p>\n<p><strong>First order of operations: Enable WordPress Comments with Login Requirement:<\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/0_wordpressCommentSettings.png\" alt=\"Wordpress Comment Settings\" height=\"500\"><\/p>\n<p><br class=\"\"><\/p>\n<p><strong>What the comment section looks like now:<\/strong><br \/>\n<img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/1_resultWordpressCommentSettings.png\" alt=\"WordpressCommentResults\" \/><\/p>\n<p>Now with the WordPress page ready, it was time to create the Identity Provider (IdP). For this I followed the <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/external-id\/customers\/quickstart-tenant-setup\">Tenant Setup Quickstart<\/a> provided by Microsoft.<\/p>\n<blockquote>\n<p><strong>Reminder: This is a separate new tenant. First step: <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity\/role-based-access-control\/security-emergency-access\">Create a Breakglass account<\/a> in case you lose access<\/strong><\/p>\n<\/blockquote>\n<p>Now that both individual parts\u2014the service provider and the identity provider\u2014are ready, it\u2019s time to make the connection!<\/p>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>Choosing an SSO Plugin<\/h2>\n<p>Out of the box, WordPress doesn\u2019t support SAML or OIDC natively. This makes sense\u2014there\u2019s likely little demand for integration with identity providers among the average bloggers or single-page users.<\/p>\n<p>Thankfully, WordPress has a large ecosystem of add-ons and plugins that can bridge this gap.<\/p>\n<p>My criteria were rather simple: <\/p>\n<ol>\n<li><strong>IdP Agnostic<\/strong>: The plugin should work with any identity provider, so tools like the Auth0 Connector Plugin were out<\/li>\n<li><strong>Open Source<\/strong>: I still strongly believe in using Open Source Software whenever possible <\/li>\n<li><strong>OIDC Protocol<\/strong>: I prefer using newer standards whenever possible <\/li>\n<\/ol>\n<p>The plugin that met all three criteria was <a href=\"https:\/\/wordpress.org\/plugins\/daggerhart-openid-connect-generic\/&quot;\">Daggerhart OpenID Connect Generic<\/a><\/p>\n<p>After installing this plugin, my WordPress test instance became OIDC-capable. From here on out, it&#8217;s just your usual Entra SSO Integration.<\/p>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>Configuring SSO in Entra (External) ID<\/h2>\n<p>First, we need the redirect URI of our plugin so we can complete the Entra Enterprise App configuration <em>(In my case in Settings &gt; OpenID Connect Client)<\/em><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/3_RedirectURL.png\" alt=\"WordpressRedirectURL\" \/><\/p>\n<p>Then, we complete the required base settings on the Entra ID side.<br \/>\nThis is in my opinion the main advantage of using Entra External ID, if you are an Entra ID admin you have seen these configurations a million times.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/2_createEnterpriseApp.png\" alt=\"EntraAuthSettings\" \/><\/p>\n<p><br class=\"\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/4_EntraInitialSettings.png\" alt=\"EntraAuthSettings\" \/><\/p>\n<hr \/>\n<p>We add our standard OIDC permissions <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/5_GrantPermissions.png\" alt=\"EntraPermissions\" \/><\/p>\n<blockquote>\n<p>\ud83d\uddb9 <em>Don&#8217;t grant admin consent &#8211; if you are onboarding end-users (e.g., blog readers) and not your employees it is very helpful to have explicit consent &#8211; Explicit user consent ensures transparency and makes data collection explicit, which is especially crucial for GDPR compliance in Europe.<\/em><\/p>\n<\/blockquote>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>Configuring the OIDC Plugin<\/h2>\n<p><u>Important! In the plugin I am using the following fields are Mandatory! Otherwise the SSO Sign-In button does nothing!<\/u><\/p>\n<blockquote>\n<p>Ideally, the <code>userinfo<\/code> and <code>end session<\/code> URLs should be dynamically read from the configuration URL: <code>https:\/\/[TenantURL].ciamlogin.com\/[TENANTID]\/v2.0\/.well-known\/openid-configuration<\/code><br \/>\nbut developers don&#8217;t always get along with standards. <\/p>\n<\/blockquote>\n<table>\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Client ID<\/td>\n<td>[Your Client ID]<\/td>\n<\/tr>\n<tr>\n<td>Client Secret Key<\/td>\n<td>[Your Client Secret]<\/td>\n<\/tr>\n<tr>\n<td>OpenID Scope<\/td>\n<td><code>profile<\/code> <code>openid<\/code> <code>offline_access<\/code><\/td>\n<\/tr>\n<tr>\n<td>Login Endpoint URL<\/td>\n<td><a href=\"https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/authorize\">https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/authorize<\/a><\/td>\n<\/tr>\n<tr>\n<td>Userinfo Endpoint URL<\/td>\n<td><a href=\"https:\/\/graph.microsoft.com\/oidc\/userinfo\">https:\/\/graph.microsoft.com\/oidc\/userinfo<\/a><\/td>\n<\/tr>\n<tr>\n<td>Token Validation Endpoint URL<\/td>\n<td><a href=\"https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/token\">https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/token<\/a><\/td>\n<\/tr>\n<tr>\n<td>End Session Endpoint URL<\/td>\n<td><a href=\"https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/logout\">https:\/\/[TENANTNAME].ciamlogin.com\/[TENANTID]\/oauth2\/v2.0\/logout<\/a><\/td>\n<\/tr>\n<tr>\n<td>Identity Key<\/td>\n<td><code>sub<\/code><\/td>\n<\/tr>\n<tr>\n<td>Nickname Key<\/td>\n<td><code>name<\/code><\/td>\n<\/tr>\n<tr>\n<td>Email Formatting<\/td>\n<td>Cleared, since I don&#8217;t want emails in my database<\/td>\n<\/tr>\n<tr>\n<td>Display Name Formatting<\/td>\n<td><code>{name}<\/code>, or the claims from wich to build the Display name<\/td>\n<\/tr>\n<tr>\n<td>State Time Limit<\/td>\n<td>360 s ( <em>With the Default 180 I was getting &quot;invalid state&quot; too often for my taste<\/em> \ud83d\ude09)<\/td>\n<\/tr>\n<tr>\n<td>Identity with User Name<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Enable Refresh Token<\/td>\n<td>Yes (This is what the scope <code>offline_access<\/code> is for)<\/td>\n<\/tr>\n<tr>\n<td>Create user if does not exist<\/td>\n<td>Yes<\/td>\n<\/tr>\n<tr>\n<td>Redirect Back to Origin Page<\/td>\n<td>Yes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>I always prefer using <code>userprincipalname<\/code> or <code>preferred_username<\/code> as the identifier, sadly the plugin I use still uses the userinfo Endpoint. This is always a bad sign for integration with Entra ID, since we <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/userinfo#notes-and-caveats-on-the-userinfo-endpoint\">can&#8217;t change the Information from the userinfo Endpoint<\/a> in Entra ID.<\/p>\n<blockquote>\n<p><em>If your solution allows working with ID tokens: I recommend this <a href=\"https:\/\/martin.rublik.eu\/2024\/10\/15\/testing-claim-rules-with-jwtms.html?utm_source=sparrow365\">Article \/ Tool for Claim Testing<\/a> from Martin Rublik, while I did fear that my plugin used userinfo from the get-go, I used the tool to verify the ID token to be correct when the SSO did not work<\/em><\/p>\n<\/blockquote>\n<p>With this configuration, users <strong>Can<\/strong> provide their email, but don&#8217;t have to &#8211; and we have successfully achieved SSO. But let&#8217;s look at the result in more detail.<\/p>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>User Experience<\/h2>\n<p>If we press the login Button that would usually redirect to the default WordPress Login, we are now immediately redirected to the Entra External ID Login experience. <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/10_pressLoginButton.png\" alt=\"pressLoginButton\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/11_signInDialog.png\" alt=\"loginMas\" \/><\/p>\n<hr \/>\n<p>Then we try logging in with a previously invited User, and can sign in with authenticator MFA, after Consenting to sharing of our information.<\/p>\n<table>\n<thead>\n<tr>\n<th>1: MFA Prompt<\/th>\n<th>2: Consent Request<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/15_goodOleAuthenticatorMFA.png\" alt=\"mfaPrompt\" \/><\/td>\n<td><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/6_consentRequest.png\" alt=\"consentREquest\" height=\"500\"><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr \/>\n<p>The User is logged in successfully, and could provide an Email Address, but it is empty by default.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/12_successfullLogin.png\" alt=\"consentPrompt\" \/><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/13_emailNotRequired.png\" alt=\"noMailInProfile\" \/><\/p>\n<hr \/>\n<p>On Logout, the Microsoft Account Picker is used.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/11\/14_logOutDiag.png\" alt=\"logout\" \/><\/p>\n<hr \/>\n<p><br class=\"\"><\/p>\n<h2>Was it worth it?<\/h2>\n<p>If you try commenting on my blog, you&#8217;ll now find a very different experience. After testing, I decided to use a <a href=\"https:\/\/nextendweb.com\/social-login\/\">plugin which specialises in Social Logins<\/a>. This solution has a much larger selection of social providers (including dedicated Login buttons) and also allows me to leave it open to the user what information he shares.<\/p>\n<p>I did not go in depth on the integration with other identity providers in this blog, since the configuration and end user experience is really not what I was looking for &#8211; but that&#8217;s okay, since Entra External ID is not meant for my use case.<\/p>\n<p>For my little blog, managing login flows, user management processes, or building custom pages is overkill. However, for developers or organizations requiring robust identity infrastructure, Entra External ID offers significant benefits by handling much of the heavy lifting.<\/p>\n<p>That said, <strong>Entra External ID still lags behind its predecessor, <a href=\"https:\/\/learn.microsoft.com\/de-de\/azure\/active-directory-b2c\/overview\">Entra B2C<\/a><\/strong>, in several areas\u2014particularly user provisioning. For example, I know of recent CIAM projects where teams, after a detailed comparison, opted for B2C due to these limitations and hope for future migration paths to External ID.<\/p>\n<p>For me personally, the primary goal of gaining experience with Entra External ID was met. I learned a great deal and gained valuable hands-on practice.<\/p>\n<p>I\u2019m excited to see where Entra External ID might be in the next couple of years\u2014perhaps it will close the gaps and become even more compelling for a wider range of use cases. \ud83d\ude0a<\/p>\n","protected":false},"excerpt":{"rendered":"<p>After realising that I should really enable comments in my last post, I set out to do so in a way that fits my personal requirements. At the outset, the most obvious solution was adding Entra External ID Authentication and user management (as one does \ud83d\ude09). Entra External ID is a version of Entra ID&#8230; &raquo; <a class=\"read-more-link\" href=\"https:\/\/sparrow365.de\/index.php\/en\/2024\/11\/29\/adding-entra-external-id-to-wordpress\/\">weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":889,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[76],"tags":[80,426,88,430,424,422,222,428],"class_list":["post-887","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-me-id-en","tag-aad-en","tag-ciam","tag-entra-en","tag-entra-b2c","tag-entra-external-id","tag-oew","tag-oidc-en","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/887","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/comments?post=887"}],"version-history":[{"count":1,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/887\/revisions"}],"predecessor-version":[{"id":888,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/887\/revisions\/888"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media\/889"}],"wp:attachment":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media?parent=887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/categories?post=887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/tags?post=887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}