Repository: wso2/identity-apps
Pull request #8175: Resolve theme from query param in auth and recovery portals
Author: login: savindi7
Created: 2025-05-08T19:06:28Z | Merged: 2025-05-20T12:25:44Z

### Purpose
Resolve theme from query param "ui_theme" in authentication and recovery portals.

### Related Issues
- https://github.com/wso2/product-is/issues/23830

### Related PRs
<!-- Mention the related pull requests. Make sure to only add publicly accessible references. -->
- N/A

### Checklist

- [ ] e2e cypress tests locally verified. (for internal contributers)
- [ ] Manual test round performed and verified.
- [ ] UX/UI review done on the final implementation.
- [ ] Documentation provided. (Add links if there are any)
- [ ] Relevant backend changes deployed and verified
- [ ] [Unit tests](/docs/testing/UNIT_TESTING.md) provided. (Add links if there are any)
- [ ] [Integration tests](https://github.com/wso2/product-is/tree/master/modules/integration) provided. (Add links if there are any)

### Security checks
- [ ] Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines
- [ ] Ran FindSecurityBugs plugin and verified report
- [ ] Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets



Diff:
diff --git a/.changeset/nasty-spoons-carry.md b/.changeset/nasty-spoons-carry.md
new file mode 100644
index 00000000000..5bb9a2fc19b
--- /dev/null
+++ b/.changeset/nasty-spoons-carry.md
@@ -0,0 +1,5 @@
+---
+"@wso2is/identity-apps-core": patch
+---
+
+Resolve theme from query param/cookie in auth and recovery portals.
diff --git a/identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp b/identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
index 7e393227406..8bcd69bc45b 100644
--- a/identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
+++ b/identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
@@ -273,6 +273,7 @@
     String SHOULD_REMOVE_ASGARDEO_BRANDING_KEY = "removeAsgardeoBranding";
     String SHOULD_REMOVE_DEFAULT_BRANDING_KEY = "removeDefaultBranding";
     String TEXT_KEY = "text";
+    String RESOLUTION_STRATEGY_KEY = "resolutionStrategy";
 
     // Additional keys to override the fallback values.
     String PRODUCT_NAME_KEY = "productName";
@@ -414,12 +415,32 @@
     String APP_PREFERENCE_RESOURCE_TYPE = "APP";
     String RESOURCE_TYPE_KEY = "type";
     String RESOURCE_NAME_KEY = "name";
+    String UI_THEME = "ui_theme";
     String preferenceResourceType = ORG_PREFERENCE_RESOURCE_TYPE;
     String tenantRequestingPreferences = tenantForTheming;
     JSONObject preferenceResolvedFrom = null;
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter(UI_THEME);
+    String themeFromCookie = null;
+
+    Cookie[] allCookies = request.getCookies();
+
+    if (allCookies != null) {
+        for (Cookie cookie : allCookies) {
+
+            if (UI_THEME.equals(cookie.getName())) {
+                themeFromCookie = cookie.getValue();
+                break;
+            }
+        }
+    }
+
+    if (StringUtils.isBlank(uiThemeParam) && StringUtils.isNotBlank(themeFromCookie)) {
+        uiThemeParam = themeFromCookie;
+    }
 
     try {
 
@@ -466,6 +487,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter(UI_THEME);
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = themeFromCookie;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(themeFromCookie)) {
+                        
+                        String cookieName = UI_THEME;
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure";
+                        response.setHeader("Set-Cookie", headerValue);
+                    }
+                }
+
                 // Custom Text
                 for (String screenName : screenNames) {
                     StringBuilder textBrandingCacheKey = new StringBuilder(BRANDING_TEXT_PREFERENCE_CACHE_KEY);
@@ -523,35 +604,51 @@
 
                 // Theme
                 if (brandingPreference.has(THEME_KEY)) {
+                    
+                    if (StringUtils.isBlank(activeThemeName) &&
+                    "APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy) &&
+                    StringUtils.isNotBlank(uiThemeParam)) {
+
+                            if (StringUtils.equalsIgnoreCase(uiThemeParam, "dark")) {
+                                activeThemeName = "DARK";
+                            } else if (StringUtils.equalsIgnoreCase(uiThemeParam, "light")) {
+                                activeThemeName = "LIGHT";
+                            } else {
+                                activeThemeName = "";
+                            }
+                    }
+
                     if (brandingPreference.getJSONObject(THEME_KEY).has(ACTIVE_THEME_KEY)) {
-                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))) {
+                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))
+                                && StringUtils.isBlank(activeThemeName)) {
                             activeThemeName = brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY);
+                        }
+                    }
 
-                            if (brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
-                                && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
+                    if (!StringUtils.isBlank(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
 
-                                theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
+                        theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
 
-                                if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
+                        if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
                                     if (theme.getJSONObject(IMAGES_KEY).has(LOGO_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
 
-                                            logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
+                                    logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
+                                }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
 
-                                            logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
-                                        }
-                                    }
+                                    logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
+                                }
+                            }
                                     if (theme.getJSONObject(IMAGES_KEY).has(FAVICON_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
-
-                                            faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                    }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
+                
+                                    faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
                                 }
                             }
                         }
diff --git a/identity-apps-core/apps/recovery-portal/src/main/webapp/includes/branding-preferences.jsp b/identity-apps-core/apps/recovery-portal/src/main/webapp/includes/branding-preferences.jsp
index 616df07c635..8abc37df543 100644
--- a/identity-apps-core/apps/recovery-portal/src/main/webapp/includes/branding-preferences.jsp
+++ b/identity-apps-core/apps/recovery-portal/src/main/webapp/includes/branding-preferences.jsp
@@ -271,6 +271,7 @@
     String SHOULD_REMOVE_ASGARDEO_BRANDING_KEY = "removeAsgardeoBranding";
     String SHOULD_REMOVE_DEFAULT_BRANDING_KEY = "removeDefaultBranding";
     String TEXT_KEY = "text";
+    String RESOLUTION_STRATEGY_KEY = "resolutionStrategy";
 
     // Additional keys to override the fallback values.
     String PRODUCT_NAME_KEY = "productName";
@@ -406,12 +407,32 @@
     String APP_PREFERENCE_RESOURCE_TYPE = "APP";
     String RESOURCE_TYPE_KEY = "type";
     String RESOURCE_NAME_KEY = "name";
+    String UI_THEME = "ui_theme";
     String preferenceResourceType = ORG_PREFERENCE_RESOURCE_TYPE;
     String tenantRequestingPreferences = tenantForTheming;
     JSONObject preferenceResolvedFrom = null;
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter(UI_THEME);
+    String themeFromCookie = null;
+
+    Cookie[] allCookies = request.getCookies();
+
+    if (allCookies != null) {
+        for (Cookie cookie : allCookies) {
+
+            if (UI_THEME.equals(cookie.getName())) {
+                themeFromCookie = cookie.getValue();
+                break;
+            }
+        }
+    }
+
+    if (StringUtils.isBlank(uiThemeParam) && StringUtils.isNotBlank(themeFromCookie)) {
+        uiThemeParam = themeFromCookie;
+    }
 
     try {
 
@@ -458,6 +479,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter(UI_THEME);
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = themeFromCookie;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(themeFromCookie)) {
+                        
+                        String cookieName = UI_THEME;
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure";
+                        response.setHeader("Set-Cookie", headerValue);
+                    }
+                }
+
                 // Custom Text
                 for (String screenName : screenNames) {
                     StringBuilder textBrandingCacheKey = new StringBuilder(BRANDING_TEXT_PREFERENCE_CACHE_KEY);
@@ -516,35 +597,50 @@
 
                 // Theme
                 if (brandingPreference.has(THEME_KEY)) {
+                    if (StringUtils.isBlank(activeThemeName) &&
+                    "APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy) &&
+                    StringUtils.isNotBlank(uiThemeParam)) {
+
+                            if (StringUtils.equalsIgnoreCase(uiThemeParam, "dark")) {
+                                activeThemeName = "DARK";
+                            } else if (StringUtils.equalsIgnoreCase(uiThemeParam, "light")) {
+                                activeThemeName = "LIGHT";
+                            } else {
+                                activeThemeName = "";
+                            }
+                    }
+
                     if (brandingPreference.getJSONObject(THEME_KEY).has(ACTIVE_THEME_KEY)) {
-                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))) {
+                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))
+                                && StringUtils.isBlank(activeThemeName)) {
                             activeThemeName = brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY);
+                        }
+                    }
 
-                            if (brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
-                                && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
+                    if (!StringUtils.isBlank(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
 
-                                theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
+                        theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
 
-                                if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
+                        if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
                                     if (theme.getJSONObject(IMAGES_KEY).has(LOGO_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
 
-                                            logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
+                                    logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
+                                }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
 
-                                            logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
-                                        }
-                                    }
+                                    logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
+                                }
+                            }
                                     if (theme.getJSONObject(IMAGES_KEY).has(FAVICON_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
-
-                                            faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                    }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
+                
+                                    faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
                                 }
                             }
                         }
diff --git a/identity-apps-core/apps/x509-certificate-authentication-portal/src/main/webapp/includes/branding-preferences.jsp b/identity-apps-core/apps/x509-certificate-authentication-portal/src/main/webapp/includes/branding-preferences.jsp
index 8a993f26de3..ce4ea50922f 100644
--- a/identity-apps-core/apps/x509-certificate-authentication-portal/src/main/webapp/includes/branding-preferences.jsp
+++ b/identity-apps-core/apps/x509-certificate-authentication-portal/src/main/webapp/includes/branding-preferences.jsp
@@ -273,6 +273,7 @@
     String SHOULD_REMOVE_ASGARDEO_BRANDING_KEY = "removeAsgardeoBranding";
     String SHOULD_REMOVE_DEFAULT_BRANDING_KEY = "removeDefaultBranding";
     String TEXT_KEY = "text";
+    String RESOLUTION_STRATEGY_KEY = "resolutionStrategy";
 
     // Additional keys to override the fallback values.
     String PRODUCT_NAME_KEY = "productName";
@@ -412,12 +413,32 @@
     String APP_PREFERENCE_RESOURCE_TYPE = "APP";
     String RESOURCE_TYPE_KEY = "type";
     String RESOURCE_NAME_KEY = "name";
+    String UI_THEME = "ui_theme";
     String preferenceResourceType = ORG_PREFERENCE_RESOURCE_TYPE;
     String tenantRequestingPreferences = tenantForTheming;
     JSONObject preferenceResolvedFrom = null;
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter(UI_THEME);
+    String themeFromCookie = null;
+
+    Cookie[] allCookies = request.getCookies();
+
+    if (allCookies != null) {
+        for (Cookie cookie : allCookies) {
+
+            if (UI_THEME.equals(cookie.getName())) {
+                themeFromCookie = cookie.getValue();
+                break;
+            }
+        }
+    }
+
+    if (StringUtils.isBlank(uiThemeParam) && StringUtils.isNotBlank(themeFromCookie)) {
+        uiThemeParam = themeFromCookie;
+    }
 
     try {
 
@@ -464,6 +485,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter(UI_THEME);
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = themeFromCookie;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(themeFromCookie)) {
+                        
+                        String cookieName = UI_THEME;
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure";
+                        response.setHeader("Set-Cookie", headerValue);
+                    }
+                }
+
                 // Custom Text
                 for (String screenName : screenNames) {
                     StringBuilder textBrandingCacheKey = new StringBuilder(BRANDING_TEXT_PREFERENCE_CACHE_KEY);
@@ -521,35 +602,51 @@
 
                 // Theme
                 if (brandingPreference.has(THEME_KEY)) {
+
+                    if (StringUtils.isBlank(activeThemeName) &&
+                    "APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy) &&
+                    StringUtils.isNotBlank(uiThemeParam)) {
+
+                            if (StringUtils.equalsIgnoreCase(uiThemeParam, "dark")) {
+                                activeThemeName = "DARK";
+                            } else if (StringUtils.equalsIgnoreCase(uiThemeParam, "light")) {
+                                activeThemeName = "LIGHT";
+                            } else {
+                                activeThemeName = "";
+                            }
+                    }
+
                     if (brandingPreference.getJSONObject(THEME_KEY).has(ACTIVE_THEME_KEY)) {
-                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))) {
+                        if (!StringUtils.isBlank(brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY))
+                                && StringUtils.isBlank(activeThemeName)) {
                             activeThemeName = brandingPreference.getJSONObject(THEME_KEY).getString(ACTIVE_THEME_KEY);
+                        }
+                    }
 
-                            if (brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
-                                && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
+                    if (!StringUtils.isBlank(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).has(activeThemeName)
+                        && brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName) != null) {
 
-                                theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
+                        theme = brandingPreference.getJSONObject(THEME_KEY).getJSONObject(activeThemeName);
 
-                                if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
+                        if (theme.has(IMAGES_KEY) && theme.getJSONObject(IMAGES_KEY) != null) {
                                     if (theme.getJSONObject(IMAGES_KEY).has(LOGO_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY))) {
 
-                                            logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
+                                    logoURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(IMAGE_URL_KEY);
+                                }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).has(ALT_TEXT_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY))) {
 
-                                            logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
-                                        }
-                                    }
+                                    logoAlt = theme.getJSONObject(IMAGES_KEY).getJSONObject(LOGO_KEY).getString(ALT_TEXT_KEY);
+                                }
+                            }
                                     if (theme.getJSONObject(IMAGES_KEY).has(FAVICON_KEY) && theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY) != null) {
-                                        if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
-                                            && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
-
-                                            faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
-                                        }
-                                    }
+                                if (theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).has(IMAGE_URL_KEY)
+                                        && !StringUtils.isBlank(theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY))) {
+                
+                                    faviconURL = theme.getJSONObject(IMAGES_KEY).getJSONObject(FAVICON_KEY).getString(IMAGE_URL_KEY);
                                 }
                             }
                         }


Reviews:
user: login: brionmario
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T05:52:04Z
user: login: brionmario
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T05:53:24Z
user: login: pavinduLakshan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T06:10:44Z
user: login: pavinduLakshan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T06:11:23Z
user: login: pavinduLakshan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T06:14:58Z
user: login: savindi7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T06:30:08Z
user: login: savindi7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T07:14:37Z
user: login: brionmario
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T07:53:22Z
user: login: pavinduLakshan
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-19T13:45:33Z

Review comments:
diff_hunk: @@ -420,6 +421,25 @@
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter("ui_theme");
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: brionmario
body: Shall we take `ui_theme` to a constant.
created_at: 2025-05-19T05:52:03Z
author_association: MEMBER
diff_hunk: @@ -466,6 +486,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter("ui_theme");
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = currentCookieTheme;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(currentCookieTheme)) {
+                        
+                        String cookieName = "ui_theme";
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure; HttpOnly; SameSite=None";
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: brionmario
body: This is setting the `ui_theme` cookie as a HTTP only cookie?
Hope that's expected. I believe now we are not accessing this cookie from JavaScript. But will we have to access it in JavaScript in the future?
created_at: 2025-05-19T05:53:24Z
author_association: MEMBER
diff_hunk: @@ -420,6 +421,25 @@
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter("ui_theme");
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: pavinduLakshan
body: Let's also encode the parameter
created_at: 2025-05-19T06:10:44Z
author_association: MEMBER
diff_hunk: @@ -420,6 +421,25 @@
     String preferenceResolvedFromResourceName = tenantRequestingPreferences;
     String applicationRequestingPreferences = spAppId;
     String locale = StringUtils.isNotBlank(getUserLocaleCode(request)) ? getUserLocaleCode(request) : DEFAULT_RESOURCE_LOCALE;
+    String resolutionStrategy = "DEFAULT";
+    String uiThemeParam = request.getParameter("ui_theme");
+    String currentCookieTheme = null;
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: pavinduLakshan
body: WDYT?

```suggestion
    String themeFromCookie = null;
```
created_at: 2025-05-19T06:11:22Z
author_association: MEMBER
diff_hunk: @@ -466,6 +486,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter("ui_theme");
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: pavinduLakshan
body: cannot we reuse the `uiThemeParam` variable here?
created_at: 2025-05-19T06:14:58Z
author_association: MEMBER
diff_hunk: @@ -466,6 +486,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter("ui_theme");
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = currentCookieTheme;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(currentCookieTheme)) {
+                        
+                        String cookieName = "ui_theme";
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure; HttpOnly; SameSite=None";
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: savindi7
body: Since SameSite is set to None I added this for extra security so JS cant access this..as of now not accessing via JS..if in the future we need to read or modify it via JS, we can remove the HttpOnly flag at that time. wdyt?
created_at: 2025-05-19T06:30:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -466,6 +486,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter("ui_theme");
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: savindi7
body: We keep resolvedUiTheme separate so we can apply the resolution order.
created_at: 2025-05-19T07:14:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -466,6 +486,66 @@
 
             // Proceed only if the branding is enabled.
             if (isBrandingEnabledInTenantPreferences) {
+
+                // Get the resolution strategy.
+                if (brandingPreference.has(THEME_KEY)) {
+                    if (brandingPreference.getJSONObject(THEME_KEY).has(RESOLUTION_STRATEGY_KEY)) {
+                        resolutionStrategy = brandingPreference.getJSONObject(THEME_KEY).getString(RESOLUTION_STRATEGY_KEY);
+                    }
+                }
+
+                // If the branding theme is set using the application.
+                if ("APP_PREFERENCE".equalsIgnoreCase(resolutionStrategy)) {
+
+                    // Check theme from the query param first.
+                    String resolvedUiTheme = request.getParameter("ui_theme");
+
+                    // If not in query param, check other parameteres.
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        String paramKey = null;
+
+                        if (StringUtils.isNotBlank(request.getParameter("callback"))) {
+                            paramKey = request.getParameter("callback");
+                        } else if (StringUtils.isNotBlank(request.getParameter("multiOptionURI"))) {
+                            paramKey = request.getParameter("multiOptionURI");
+                        } else if (StringUtils.isNotBlank(request.getParameter("urlQuery"))) {
+                            paramKey = request.getParameter("urlQuery");
+                        }
+
+                        if (StringUtils.isNotBlank(paramKey) && paramKey.contains("ui_theme=")) {
+                            try {
+                                String[] parts = paramKey.split("[?&]");
+                                for (String part : parts) {
+                                    if (part.startsWith("ui_theme=")) {
+                                        resolvedUiTheme = part.split("=")[1];
+                                        break;
+                                    }
+                                }
+                            } catch (Exception e) {
+                                // Silent fallback
+                            }
+                        }
+                    }
+
+                    // Fallback to cookie
+                    if (StringUtils.isBlank(resolvedUiTheme)) {
+                        resolvedUiTheme = currentCookieTheme;
+                    }
+
+                    uiThemeParam = resolvedUiTheme;
+
+                    // Write cookie only if resolved value is non-blank and different from cookie
+                    if (StringUtils.isNotBlank(resolvedUiTheme) && !resolvedUiTheme.equals(currentCookieTheme)) {
+                        
+                        String cookieName = "ui_theme";
+                        String cookieValue = resolvedUiTheme;
+                        int maxAge = 2592000;
+                    
+                        String headerValue = cookieName + "=" + cookieValue + "; Path=/; Max-Age=" + maxAge + "; Secure; HttpOnly; SameSite=None";
path: identity-apps-core/apps/authentication-portal/src/main/webapp/includes/branding-preferences.jsp
user: login: brionmario
body: Shall we keep this non-HttpOnly from now on?
So that we don't have to suddenly change the cookie property later?

Since we are not storing any confidential values here in this cookie, IMO we can just keep this non-HTTP Only.
created_at: 2025-05-19T07:53:22Z
author_association: MEMBER

PR comments:
user: login: wso2-jenkins-bot
created_at: 2025-05-19T12:33:26Z
body: <h3>🦋 Changeset detected</h3><p><b>The changes in this PR will be included in the next version bump.</b></p><p>Not sure what this means? <a href="https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md">Click here to learn what changesets are</a>.</p>
author_association: CONTRIBUTOR