vaadin / flow

Vaadin Flow is a Java framework binding Vaadin web components to Java. This is part of Vaadin 10+.
Apache License 2.0
590 stars 164 forks source link

Vaadin Heartbeat causing OIDC State Mismatch after Keycloak Login in Wildfly Elytron #18220

Open Sboti03 opened 7 months ago

Sboti03 commented 7 months ago

Description of the bug

After logging in, the page throws a bad_request error. The issue arises from Vaadin sending a heartbeat request after redirecting to the Single Sign-On (SSO), subsequently overriding the state parameter.

Expected behavior

Vaadin only sends heartbeat requests if the user is authenticated

Minimal reproducible example

  1. Upon loading the URL, the Wildfly Elytron HTTP OIDC submodule captures the request.
  2. The submodule identifies the request as unauthenticated and redirects it to the Single Sign-On (SSO) server (Keycloak).
  3. After the redirect Vaadin sends a heartbeat request, overwriting the originally generated state parameter.
  4. After a successful login on Keycloak, when the application is redirected back, a bad_request error occurs due to the mismatched state parameters.

Versions

mshabarov commented 7 months ago

Vaadin only sends heartbeat requests if the user is authenticated

Heartbeat is used to let Vaadin server-side know that the client-side UI is still alive and it is needed for all views/routes, including public ones. Thus, I'm not sure this should be expected with the current design.

We need to check if we have any similar problems with Vaadin SSO Kit.

How did you configure your access control layer with Wildfly? For Spring Security integration, Vaadin checks RequestUtil::isFrameworkInternalRequest(HttpServletRequest request) to know if the incoming request is Vaadin internal one, including heartbeat request, so probably you can add this check into your security configuration and it would pass these requests, see https://github.com/vaadin/flow/blob/505b7973b766ed1ba57cac9d4cdd9b9d0b0d59d1/vaadin-spring/src/main/java/com/vaadin/flow/spring/security/RequestUtil.java#L69

Sboti03 commented 7 months ago

We use WildFly Elytron Security with the default oidc module, we include the oidc.json configuration file along with our deployments

web.xml contains:

<display-name>app</display-name>
<distributable/>

<security-constraint>
<web-resource-collection>
    <web-resource-name>APP</web-resource-name>
    <url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
    <role-name>all-us</role-name>
</auth-constraint>
</security-constraint>

<security-role>
    <role-name>all-us</role-name>
</security-role>

<login-config>
    <auth-method>OIDC</auth-method>
</login-config>

<session-config>
    <session-timeout>180</session-timeout>
</session-config>

oidc.json:


{
  "provider-url": "${keycloak.url}realms/${keycloak.realm.name}",
  "ssl-required": "${keycloak.ssl.required}",
  "confidential-port": ${keycloak.confidential.port},
  "client-id": "${keycloak.client.name}",
  "verify-token-audience": true,
  "credentials": {
    "secret": "${keycloak.client.secret}"
  },
  "redirect-rewrite-rules" : {
    "^/app/(.*)$" : "/$1"
  }
}

standalone.xml

<subsystem xmlns="urn:wildfly:elytron:18.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
            <providers>
                <aggregate-providers name="combined-providers">
                    <providers name="elytron"/>
                    <providers name="openssl"/>
                </aggregate-providers>
                <provider-loader name="elytron" module="org.wildfly.security.elytron"/>
                <provider-loader name="openssl" module="org.wildfly.openssl"/>
            </providers>
            <audit-logging>
                <file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
            </audit-logging>
            <security-domains>
                <security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
                    <realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
                    <realm name="local"/>
                </security-domain>
                <security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
                    <realm name="ManagementRealm" role-decoder="groups-to-roles"/>
                    <realm name="local" role-mapper="super-user-mapper"/>
                </security-domain>
            </security-domains>
            <security-realms>
                <identity-realm name="local" identity="$local"/>
                <properties-realm name="ApplicationRealm">
                    <users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
                    <groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
                </properties-realm>
                <properties-realm name="ManagementRealm">
                    <users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
                    <groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
                </properties-realm>
            </security-realms>
            <mappers>
                <simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
                    <permission-mapping>
                        <principal name="anonymous"/>
                        <permission-set name="default-permissions"/>
                    </permission-mapping>
                    <permission-mapping match-all="true">
                        <permission-set name="login-permission"/>
                        <permission-set name="default-permissions"/>
                    </permission-mapping>
                </simple-permission-mapper>
                <constant-realm-mapper name="local" realm-name="local"/>
                <simple-role-decoder name="groups-to-roles" attribute="groups"/>
                <constant-role-mapper name="super-user-mapper">
                    <role name="SuperUser"/>
                </constant-role-mapper>
            </mappers>
            <permission-sets>
                <permission-set name="login-permission">
                    <permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
                </permission-set>
                <permission-set name="default-permissions">
                    <permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
                    <permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
                    <permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
                </permission-set>
            </permission-sets>
            <http>
                <http-authentication-factory name="application-http-authentication" security-domain="ApplicationDomain" http-server-mechanism-factory="global">
                    <mechanism-configuration>
                        <mechanism mechanism-name="BASIC">
                            <mechanism-realm realm-name="ApplicationRealm"/>
                        </mechanism>
                    </mechanism-configuration>
                </http-authentication-factory>
                <http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
                    <mechanism-configuration>
                        <mechanism mechanism-name="DIGEST">
                            <mechanism-realm realm-name="ManagementRealm"/>
                        </mechanism>
                    </mechanism-configuration>
                </http-authentication-factory>
                <provider-http-server-mechanism-factory name="global"/>
            </http>
            <sasl>
                <sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
                    <mechanism-configuration>
                        <mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
                        <mechanism mechanism-name="DIGEST-MD5">
                            <mechanism-realm realm-name="ApplicationRealm"/>
                        </mechanism>
                    </mechanism-configuration>
                </sasl-authentication-factory>
                <sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
                    <mechanism-configuration>
                        <mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
                        <mechanism mechanism-name="DIGEST-MD5">
                            <mechanism-realm realm-name="ManagementRealm"/>
                        </mechanism>
                    </mechanism-configuration>
                </sasl-authentication-factory>
                <configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
                    <properties>
                        <property name="wildfly.sasl.local-user.default-user" value="$local"/>
                        <property name="wildfly.sasl.local-user.challenge-path" value="${jboss.server.temp.dir}/auth"/>
                    </properties>
                </configurable-sasl-server-factory>
                <mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
                    <filters>
                        <filter provider-name="WildFlyElytron"/>
                    </filters>
                </mechanism-provider-filtering-sasl-server-factory>
                <provider-sasl-server-factory name="global"/>
            </sasl>
            <tls>
                <key-stores>
                    <key-store name="applicationKS">
                        <credential-reference clear-text="password"/>
                        <implementation type="JKS"/>
                        <file path="application.keystore" relative-to="jboss.server.config.dir"/>
                    </key-store>
                </key-stores>
                <key-managers>
                    <key-manager name="applicationKM" key-store="applicationKS" generate-self-signed-certificate-host="localhost">
                        <credential-reference clear-text="password"/>
                    </key-manager>
                </key-managers>
                <server-ssl-contexts>
                    <server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
                </server-ssl-contexts>
            </tls>
            <policy name="jacc">
                <jacc-policy/>
            </policy>
        </subsystem>
        <subsystem xmlns="urn:wildfly:elytron-oidc-client:2.0"/>