Repository navigation
Add Wildcard Subdomain Matching Support for Organization Domains - #45190
Conversation
cd4a99c to
eea5e2d
Compare
|
@shaidar - thank you for the PR. Can you please update the PR contain only contain your commits? This can probably be done by rebasing it. Once you have done that, there is no need for further rebases until a reviewer asks for you for a rebase, or GitHub reports merge conflicts. |
|
@keycloak/core-iam - can you please have a look at this PR? Thanks! |
| <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd"> | ||
|
|
||
| <changeSet author="keycloak" id="27.0.0-org-domain-wildcard"> |
There was a problem hiding this comment.
I would just change this to 26.6.0 (along with the filename), as Keycloak 27 won't be released in the near future.
|
@ahus1 changes look ok to me, apart from the jpa changeset version that I would switch from 27.0.0 to 26.6.0. We can trigger the CI to see if there's anything else that needs to be taken care of |
dba3465 to
e939143
Compare
|
@shaidar looks like there's a compilation failure - can you take a look and update the PR? |
a149e02 to
275ee14
Compare
|
@shaidar build is failing due to wrong import order in one of the files. Can you run |
|
Added a commit to fix the code format errors and changes to the liquibase changelog. |
a4f3f68 to
ea87513
Compare
2ce3b03 to
0189943
Compare
e9953d9 to
1aaee8c
Compare
1aaee8c to
2151508
Compare
2151508 to
e499675
Compare
01f0412 to
3a29004
Compare
|
@shaidar, would you mind reviewing and trying out the latest changes to check if it is still aligned with your expectations? |
94fa341 to
15cda50
Compare
Signed-off-by: sar <sar.haidar@gmail.com>
Problem Statement
Closes #40026
This addresses this issue. When using the Organizations feature in Keycloak, domain-based redirection to Identity Providers (IdPs) only works with exact domain matches. This creates problems in real-world scenarios where users authenticate with email addresses containing subdomains (e.g.,
user@sub.example.com) while the organization is configured with the base domain (example.com).Common use cases that were previously unsupported:
user@contoso.onmicrosoft.comwhen org is configured foronmicrosoft.comuser@cs.university.eduwhen org usesuniversity.eduThis resulted in authentication failures, NullPointerExceptions, or users being unable to access their organization.
Solution
Added a new
matchSubdomainsboolean flag to organization domains that enables hierarchical subdomain matching. When enabled for a domain likeexample.com, it will match:example.com(exact match still works)sub.example.comdeep.sub.example.comThe feature is opt-in per domain and backward compatible - all existing domains default to exact matching.
Implementation Details
Architecture
The implementation follows a simple, performant approach:
Files Modified
1. Data Model Layer (3 files)
server-spi/src/main/java/org/keycloak/models/OrganizationDomainModel.javamatchSubdomainsboolean field with default valuefalseisMatchSubdomains()gettercore/src/main/java/org/keycloak/representations/idm/OrganizationDomainRepresentation.javamatchSubdomainsfield to REST API representationmodel/jpa/src/main/java/org/keycloak/models/jpa/entities/OrganizationDomainEntity.javaMATCH_SUBDOMAINScolumn mapping2. Database Layer (2 files)
model/jpa/src/main/resources/META-INF/jpa-changelog-27.0.0.xml(new)MATCH_SUBDOMAINScolumn with defaultfalsemodel/jpa/src/main/resources/META-INF/jpa-changelog-master.xml3. Data Mapping Layer (3 files)
model/jpa/src/main/java/org/keycloak/organization/jpa/OrganizationAdapter.javatoModel()to includematchSubdomainswhen converting entity to modelsetDomains()to persistmatchSubdomainsflagmodel/jpa/src/main/java/org/keycloak/organization/jpa/JpaOrganizationProvider.javagetByDomainName()with hierarchical lookup:deep.sub.example.comchecksexample.com,sub.example.comserver-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.javatoModel()conversion to includematchSubdomainsfield4. Business Logic Layer (3 files)
services/src/main/java/org/keycloak/organization/utils/Organizations.javadomainMatches(String emailDomain, OrganizationDomainModel orgDomain)helper methodmatchesOrganizationDomain()to use new helperservices/src/main/java/org/keycloak/organization/authentication/authenticators/browser/OrganizationAuthenticator.javaredirect()method to use wildcard-aware domain matchingservices/src/main/java/org/keycloak/organization/validator/OrganizationMemberValidator.javavalidateEmailDomain()to support wildcard matchingvalidateEmailDomainMatch()helper methodDatabase Schema
Testing
Unit Tests (1 new file)
Created
OrganizationDomainMatchingTest.javawith 8 comprehensive unit tests:Integration Tests (10 new tests)
Added to existing test classes:
OrganizationTest.java:
testDomainWithWildcardSubdomains()- CRUD operations for wildcard flagAbstractBrokerSelfRegistrationTest.java:
testRedirectToIdentityProviderWithWildcardSubdomainMatching()- Subdomain redirecttestRedirectToIdentityProviderWithDeepSubdomain()- Multi-level subdomain redirecttestNoRedirectWithoutWildcardSubdomainMatching()- Verify disabled wildcard blocks subdomainstestExactDomainStillWorksWithWildcardEnabled()- Exact match with wildcard enabledtestEmailDomainValidationWithWildcardSubdomains()- Member creation with subdomaintestEmailDomainValidationRejectsSubdomainWithoutWildcard()- Rejection when disabledtestDeepSubdomainValidationWithWildcard()- Multi-level validationAll tests pass and follow Keycloak testing patterns.
Usage
REST API
Create organization with wildcard subdomain matching:
Update existing domain:
Admin Console
The
matchSubdomainsfield will be available in the organization domain configuration UI (requires separate UI changes).Behavior
With
matchSubdomains: false(default):john@example.com→ Matches ✅john@sub.example.com→ Does not match ❌With
matchSubdomains: true:john@example.com→ Matches ✅john@sub.example.com→ Matches ✅john@team.dev.example.com→ Matches ✅Backward Compatibility
✅ Fully backward compatible:
matchSubdomains: falsematchSubdomainsfield continue to workMigration Notes
For administrators:
onmicrosoft.comor organizational base domainsFor developers:
OrganizationDomainModelconstructor available withmatchSubdomainsparameterfalse)Organizations.domainMatches()helper for custom domain matching logicPerformance Considerations
sub.example.com→example.com)Security Considerations
✅ Safe by default - wildcard matching must be explicitly enabled
✅ Domain validation - existing domain validation rules still apply
✅ No new attack vectors - only matches legitimate DNS subdomain hierarchy
✅ Verification required - verified domains should be verified before enabling wildcards
Related Issues
Resolves issues with:
Checklist