Skip to content

Conversation

@vbelouso
Copy link
Contributor

@vbelouso vbelouso commented Jan 6, 2026

Adds support for external OIDC providers (Keycloak, Google, etc) via new external-idp profile.

Changes

Authentication:

  • New external-idp profile for external identity providers
  • Local-only logout using Clear-Site-Data header (no IdP session termination)
  • User display priority: email -> upn -> metadata.name -> anonymous

Documentation:

  • docs/authentication.md - comprehensive auth configuration guide
  • Updated docs/configuration.md and docs/development.md with links

Testing:

  • scripts/test-auth.sh - automated testing for all auth scenarios
  • Supports: DevServices, Keycloak (standalone/broker), Direct Google OIDC

@vbelouso vbelouso self-assigned this Jan 6, 2026
@vbelouso vbelouso added documentation Improvements or additions to documentation enhancement New feature or request labels Jan 6, 2026
@vbelouso vbelouso requested a review from zvigrinberg January 7, 2026 10:28
@zvigrinberg
Copy link
Collaborator

@vbelouso This looks stunning!
I'll review it and will take a closer look tomorrow when i'll have some time.

quarkus.http.auth.permission.authenticated.policy=authenticated
# --- PROFILE: external-idp (External Identity Provider) ---
# Supports: Keycloak (standalone or broker), GitHub, Google, Auth0, etc.
%external-idp.quarkus.oidc.application-type=web-app
Copy link
Collaborator

@zvigrinberg zvigrinberg Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vbelouso This one mean that only authorization code flow auth will be performed in the browser for the front-end web side, but not authentication using token/jwt on the rest resources' endpoints ( like now in prod profile, that you needs a jwt access token to invoke the service or the route of the rest endpoints from the outside).
The question is, do we want that or not?
IMO, we should at least keep the situation like it was before ( only authentication for rest API endpoints ( not authorization)) using a permanent JWT generated by the auth service that never expires or expires within a predefined period, and then we'll use it as a secret in the client deployment , Or we can take it to the next level and let the user/consumer of the api endpoints to pass the auth bearer JWT token themselves ( after they authenticated in front of auth service/keycloak' token endpoint themselves /realms/<realm-name>/protocol/openid-connect/token and got a JWT access token), but we'll need to revert it the oidc application type also to hybrid.

instead of it, Another option is that we can also revert it to hybrid back, and enable permissions based on scopes or just based on roles that the users having , we should think about that.
We can lookup the various options at the relevant references:
https://quarkus.io/guides/security-oidc-bearer-token-authentication
https://quarkus.io/guides/security-oidc-bearer-token-authentication-tutorial

Also we can take it one step ahead and manage all permissions in keycloak as authorization service( for external idp's only if they're supporting authorization services ( permissions , roles , client scopes, etc.):
https://quarkus.io/guides/security-keycloak-authorization.

For that maybe we can think for another profile of keycloak alone, and the external-idp profile can remain for keycloak broker (delegates authentication to external idps) and for direct external idps.

Copy link
Collaborator

@zvigrinberg zvigrinberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vbelouso This is fantastic job you've done - still need to test it, but probably will have time only on sunday ( most of the things looks right)
Only 2 things are missing, and one comment above about bearer-token-authentication for rest api endpoints :

  1. When building the native image, it's being built with default profile ( prod ), and hence afterwards, when setting/passing at runtime a property of different profile or overriding env var of QUARKUS_PROFILE , then if the runtime profile is different, it might cause problems and shock stability, So you need to add to documentation that when building an executable ( native or JVM Jar one), regardless if it's in the command line or in the Various Dockerfiles variants, to add the build time property to the build process. for example , in:
    https://github.com/RHEcosystemAppEng/agent-morpheus-client/blob/86eaa51017fdf86af8280cf0401aee27ee5870e9/docs/development.md#creating-a-native-executable
    You should add the build time property of the new profile so it will work correctly without side effects.
./mvnw package -Dnative -Dquarkus.profile=external-idp

  1. The Bean JkwsRequestFilter should be scoped only to the profile of prod, as the under the new profile, the quarkus.oidc.jwks-path property is not set, hence this bean is redundant and should be annotated with annotation @IfBuildProfile

@ApplicationScoped
@OidcEndpoint(value = Type.JWKS)
@Unremovable
public class JkwsRequestFilter implements OidcRequestFilter {
private static final Logger LOGGER = Logger.getLogger(JkwsRequestFilter.class);
private static final Path SA_TOKEN_PATH = Path.of("/var/run/secrets/kubernetes.io/serviceaccount/token");
Optional<String> token = Optional.empty();
@PostConstruct
void loadToken() {
if (Files.exists(SA_TOKEN_PATH)) {
try {
token = Optional.of(Files.readString(SA_TOKEN_PATH));
} catch (IOException e) {
LOGGER.errorf("Unable to read Service Account Token: %s", SA_TOKEN_PATH.toString(), e);
}
}
}
@Override
public void filter(OidcRequestContext requestContext) {
HttpMethod method = requestContext.request().method();
String uri = requestContext.request().uri();
if (method == HttpMethod.GET && uri.endsWith("/jwks") && token.isPresent()) {
requestContext.request().bearerTokenAuthentication(token.get());
}
}
}

In addition, You can still use something similar for authentication and validation of JWT for every request , but then the suffix of the jwks endpoint of keycloak may be different, because you're using quarkus.oidc.discovery-enabled=true, then the jwks endpoint can be retrieved from realms/your-realm/.well-known/openid-configuration endpoint of the server ( and by default it's not ending with /jwks , in keycloak server, for instance, by default it's /realms/realm_name/protocol/openid-connect/certs)

In addition, in application-type=web-app ( and not hybrid) there will be no call to the jwks endpoint by the oidc client, ( not for bear token authentication + validation, only for requests of authorization code flow auth for authentication in the browser), So you need to implement an HTTP request filter, that will intercept all requests came from remote addresses ( not localhost, this will be for free), and then it will take the auth header' token value => Authorization: Bearer $TOKEN and validate it against the jwks endpoint of the auth-server(e.g keycloak), if it'll be validated and verified, then it will continue to the request handler, otherwise, there will be a response of 401 or 403 ( based on what returning from the realms/your-realm/.well-known/openid-configuration' jwks endpoint)

Then users/clients will need to take/get token generated in the auth server ( keycloak) and will use them in Authorization Header as bearer token.

But in order to let this logic/mechanism to only run in the new profile, then you'll need to have a special boolean property set on the level of the new profile name ( in application.properties), so it will not affect the dev/prod/test profiles ( and then if this property value is disabled , you just skip the filter logic and nothing happened).

Thanks for your efforts!.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants