Skip to content

[jaxrs-spec][quarkus] - Emit Authentication & Authorisation annotations (@Authenticated, @RolesAllowed, @PermitAll) from Security Schemes #23691

@Ignacio-Vidal

Description

@Ignacio-Vidal

Problem Description

The [jaxrs-spec][quarkus] library already emits MicroProfile OpenAPI security scheme annotations (@Securityscheme, @securityrequirement) on API interfaces. However, these annotations have no effect at runtime, and are for documentation-only purpose in Swagger UI.

As a result, the developer must manually add Quarkus security annotations after generation.

This is a proposal to agree the mapping from OpenAPI Security Schemes to Quarkus Authentication/Authorisation mechanisms to emit the annotations during code generation.

Once agreed, I'm happy to contribute the PRs listed below.

Benefits of emitting Authenticaion & Authorisation annotations

  • Security by default: generated stubs are immediately deployable with correct access control; developers do not have to remember to add annotations manually
  • Spec as the source of truth: when the OpenAPI document changes (e.g., a scope is added or removed), regenerating the client automatically updates the security annotations
  • Quarkus-native developer experience: generated code uses idiomatic Quarkus/Jakarta EE security annotations that developers already know, rather than requiring them to understand how to translate OpenAPI security schemes into Quarkus constructs
  • Reduced security misconfigurations: missing or incorrect hand-written security annotations are a common source of accidental endpoint exposure; generator-driven annotations eliminate this class of mistake

Solution

Quarkus implements security based on Jakarta EE security annotations jakarta.annotation.security and its own extensions in io.quarkus.security. These annotations are enforced by Quarkus interceptors at request time.

Annotation Package Description
@Authenticated io.quarkus.security Permits any authenticated user. Equivalent to @RolesAllowed("**").
@RolesAllowed({"role1", "role2"}) jakarta.annotation.security Permits users that have at least one of the listed roles (OR semantics within the annotation).
@PermitAll jakarta.annotation.security Permits all users, including unauthenticated ones. Used to mark explicitly public endpoints.
@DenyAll jakarta.annotation.security Denies all access regardless of identity. Used to mark endpoints that must never be called directly via HTTP.
@PermissionsAllowed io.quarkus.security Fine-grained permission check against SecurityIdentity permissions. Beyond the scope of generator support.

Annotation placement

Annotations can be placed on JAX-RS interface methods or on implementation classes. Method-level annotations take precedence over class-level annotations. Quarkus supports annotations on the interface when the implementation class is not directly annotated.

Interaction with security configuration in application.yaml

quarkus.http.auth.* configuration-based policies (application.yaml) are evaluated before the security annotations. This means @PermitAll does not bypass HTTP-level security configurations from the application.yaml configuration

The generator focuses on annotation-based security, which is the standard approach for JAX-RS endpoint security in Quarkus.

Mapping table

OpenAPI security requirement Quarkus annotation Rationale
Operation has no security requirement @PermitAll Explicitly marks the endpoint as public so auditors and tooling can identify intentionally open endpoints.
http scheme, basic @Authenticated Basic authentication validates identity; there are no roles or scopes to check. Any valid credential grants access.
http scheme, bearer @Authenticated A Bearer token (e.g., JWT) validates identity. Role/scope enforcement is handled by the OIDC/JWT extension separately; the annotation marks the intent.
apiKey @Authenticated An API key validates identity only. No role check is applicable.
oauth2 with empty scopes ([]) @Authenticated An empty scope list means "any authenticated user" — no specific authorization is required beyond a valid token.
openIdConnect with empty scopes ([]) @Authenticated Same reasoning as OAuth2 with empty scopes.
oauth2 with explicit scopes @RolesAllowed({"scope1", "scope2"}) In Quarkus, OAuth2/OIDC token scopes are mapped to SecurityIdentity roles. @RolesAllowed receives the scopes as role names.
openIdConnect with explicit scopes @RolesAllowed({"scope1"}) Same as OAuth2 with scopes.
OR list with at least one empty-scope scheme @Authenticated The least restrictive alternative dominates: if any scheme allows any authenticated user, the effective gate is authentication only.
OR list where all alternatives have scopes @RolesAllowed(union_of_all_scopes) The union of scopes across all OR-alternatives is passed to @RolesAllowed. Since @RolesAllowed itself uses OR semantics (user needs any listed role), this correctly allows a user to satisfy any one of the OR-alternatives.

Limitation: OpenAPI OR semantics vs. Quarkus AND annotations

OpenAPI's security array is OR: a request is authorized if any one of the listed security alternatives is satisfied. Quarkus standard annotations use AND semantics when stacked: @Authenticated + @RolesAllowed("admin") requires the user to be both authenticated and have the admin role.

This means the two models cannot always be reconciled. The generator's strategy is to emit the least restrictive annotation that is still correct for the OR group:

  • If any alternative has empty scopes → @Authenticated (any authenticated user passes; specific scopes in other alternatives are not enforced at the annotation level)
  • If all alternatives have explicit scopes → @RolesAllowed(union_of_all_scopes) (user needs any one of the scopes, matching any one alternative)

Never emit both @Authenticated and @RolesAllowed on the same method: Quarkus would apply both interceptors, creating AND semantics stricter than the spec intends.

Delivery Plan

List of PRs to incrementally add the security annotations support:

#1 - Emit @io.quarkus.security.Authenticated` on JAX-RS interface methods and implementation stubs when an operation's security has:

  • http: basic authentication
  • apiKey authentication
  • http: bearer authentication
  • oauth2 or openIdConnect with empty scopes ([])
  • an OR list where at least one alternative has empty scopes
security:
  - oauth2_read: [read:items]
  - oauth2_admin: []
@io.quarkus.security.Authenticated
@GET
@Path("/items")
Response listItems();

#2 - Emit @RolesAllowed for operations where all OR-alternatives have explicit scopes. Example:

security:
  - oauth2_read: [read:items]
  - oauth2_admin: [admin]
@jakarta.annotation.security.RolesAllowed({"read:items", "admin"})
@GET
@Path("/items")
Response listItems();

#3 - Emit @PermitAll for operations with no security requirement

@jakarta.annotation.security.PermitAll
@GET
@Path("/health")
Response healthCheck();

Considerations:

  • @PermissionsAllowed: Quarkus's io.quarkus.security.PermissionsAllowed supports fine-grained permission checks beyond role names. Mapping OpenAPI scopes to permissions (as opposed to roles) requires knowing how the application configures its identity provider. This is out of scope for the generator and left to the developer.
  • quarkus.security.jaxrs.deny-unannotated-endpoints: The generator could emit a reminder comment or a documentation note suggesting users enable this property alongside the generated annotations for defense-in-depth.
  • Multi-tenant OIDC: Quarkus supports multi-tenant OIDC (quarkus-oidc). The mapping from an OpenAPI openIdConnect scheme to a specific tenant is not expressible in annotations alone; this is a known limitation.
  • Global security overrides: OpenAPI allows a global security block at the document level, with per-operation overrides. The generator should ensure it resolves effective security for each operation (global default minus per-operation override) before applying annotation logic. This needs verification in the existing implementation.
  • @DenyAll: No mapping is proposed from OpenAPI constructs to @DenyAll. If needed, it could be expressed via a vendor extension (x-quarkus-deny-all: true) for operations that should be unreachable via HTTP.

Alternatives considered

The only alternative is to keep the status quo where developers need to add the Authentication and Authorisation annotations post code generation

Open Questions:

  • Should it introduce a new generator flag to enable/disable emitting the security annotations? This avoids breaking changes for teams already manually managing the security annotations separately from the code generation

Labels

enhancement, jaxrs-spec, quarkus, security

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions