Security Providers

Helidon Security providers

Implemented Security Providers

Helidon provides the following security providers for endpoint protection:

ProviderTypeOutbound supportedDescription
OIDC ProviderAuthenticationOpen ID Connect supporting JWT, Scopes, Groups and OIDC code flow
HTTP Basic AuthenticationAuthenticationHTTP Basic authentication for local testing and demos
Header AssertionAuthenticationAsserting a user based on a header value
HTTP SignaturesAuthenticationProtecting service to service communication through signatures
ABAC AuthorizationAuthorization🚫Attribute based access control authorization policies

The following providers are no longer evolved:

ProviderTypeOutbound supportedDescription
JWT ProviderAuthenticationJWT tokens passed from frontend

OIDC Provider

Open ID Connect security provider.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-oidc</artifactId>
</dependency>

Usage

In Helidon, we need to register the redirection support with routing (in addition to SecurityFeature that integrates with WebServer). This is not required when redirect is set to false.

Adding support for OIDC redirects

WebServer.builder()
    .addFeature(SecurityFeature.builder()
        .config(config.get("security"))
        .build())
    .routing(r -> r.addFeature(OidcFeature.create(config)))
    .build();

Configuration options

KeyTypeDefaultDescription
access-token-ip-checkBooleantrueWhether to check if current IP address matches the one access token was issued for
client-credentials-configClientCredentialsConfigSet the configuration related to the client credentials flow
cookie-compression-enabledBooleantrue if server-type is idcs; false otherwiseWhether to GZIP-compress the access token cookie when this reduces its size, unless password-based legacy cookie encryption is enabled
cookie-compression-id-enabledBooleantrue if server-type is idcs; false otherwiseWhether to GZIP-compress the ID token cookie when this reduces its size, unless password-based legacy cookie encryption is enabled
cookie-domainStringDomain the cookie is valid for
cookie-encryption-enabledBooleantrueWhether to encrypt token cookie created by this microservice
cookie-encryption-id-enabledBooleantrueWhether to encrypt id token cookie created by this microservice
cookie-encryption-nameStringName of the encryption configuration available through Security#encrypt(String, byte[]) and Security#decrypt(String, String)
cookie-encryption-passwordList<String>Master password for encryption/decryption of cookies
cookie-encryption-refresh-enabledBooleantrueWhether to encrypt refresh token cookie created by this microservice
cookie-encryption-state-enabledBooleantrueWhether to encrypt state cookie created by this microservice
cookie-encryption-tenant-enabledBooleantrueWhether to encrypt tenant name cookie created by this microservice
cookie-http-onlyBooleantrueWhen using cookie, if set to true, the HttpOnly attribute will be configured
cookie-max-age-secondsLongWhen using cookie, used to set MaxAge attribute of the cookie, defining how long the cookie is valid
cookie-nameStringJSESSIONIDName of the cookie to use
cookie-name-id-tokenStringJSESSIONID_2Name of the cookie to use for id token
cookie-name-refresh-tokenStringJSESSIONID_3The name of the cookie to use for the refresh token
cookie-name-stateStringJSESSIONID_3The name of the cookie to use for the state storage
cookie-name-tenantStringHELIDON_TENANTThe name of the cookie to use for the tenant name
cookie-pathString/Path the cookie is valid for
cookie-same-siteSameSiteLAXWhen using cookie, used to set the SameSite cookie value
cookie-secureBooleanfalseWhen using cookie, if set to true, the Secure attribute will be configured
cookie-useBooleantrueWhether to use cookie to store JWT between requests
fallback-to-default-tenant-enabledBooleanfalseWhether unknown tenant ids should use default tenant configuration
force-https-redirectsBooleanfalseForce HTTPS for redirects to identity provider
frontend-uriStringFull URI of this application that is visible from user browser
header-tokenTokenHandlerA TokenHandler to process header containing a JWT
header-useBooleantrueWhether to expect JWT in a header field
id-token-signature-validationBooleantrueWhether id token signature check should be enabled
jwk-loaderConfiguration for jwk-loader
jwt-groups-pathStringgroupsPath to the JWT payload claim containing the groups to add as role grants
jwt-groups-separatorStringSeparator used to split a string claim value into multiple groups
legacy-cookie-encryptionBooleanfalseWhether password-based encrypted OIDC cookies should be written without a version byte and with the legacy PBKDF2 iteration count
legacy-cookie-fallbackBooleanfalseWhether password-based encrypted OIDC cookies should retry decryption with the alternate cookie format after primary decryption fails
max-redirectsInteger5Configure maximal number of redirects when redirecting to an OIDC provider within a single authentication attempt
optionalBooleanfalseWhether authentication is required
outboundList<OutboundTarget>Add a new target configuration
outbound-typeOidcOutboundTypeUSER_JWTType of the OIDC outbound
pkce-challenge-methodPkceChallengeMethodS256Proof Key Code Exchange (PKCE) challenge creation method
pkce-enabledBooleanfalseWhether this provider should support PKCE
propagateBooleanfalseWhether to propagate identity
query-id-token-param-nameStringid_tokenName of a query parameter that contains the JWT id token when parameter is used
query-param-nameStringaccessTokenName of a query parameter that contains the JWT access token when parameter is used
query-param-tenant-nameStringh_tenantName of a query parameter that contains the tenant name when the parameter is used
query-param-useBooleanfalseWhether to use a query parameter to send JWT token from application to this server
redirectBooleanfalseBy default, the client should redirect to the identity server for the user to log in
redirect-attempt-counter-strategyRedirectAttemptCounterStrategyPARAMConfigure the strategy used to count redirects to an identity server
redirect-attempt-paramStringh_raConfigure the redirect attempt query parameter and cookie name prefix
redirect-uriString/oidc/redirectURI to register web server component on, used by the OIDC server to redirect authorization requests to after a user logs in or approves scopes
tenantsTenantConfigConfigurations of the tenants
token-signature-validationBooleantrueWhether access token signature check should be enabled
use-jwt-groupsBooleantrueClaim groups from JWT will be used to automatically add groups to current subject (may be used with jakarta.annotation.security.RolesAllowed annotation)
webclientWebClientWebClient configuration used for outbound requests to the identity server. This configuration sets the values to the OIDC WebClient default configuration

Configuration Example

application.yaml
security:
  providers:
  - oidc:
      client-id: "client-id-of-this-service"
      client-secret: "${CLEAR=changeit}"
      identity-uri: "https://your-tenant.identity-server.com"
      frontend-uri: "http://my-service:8080"
      audience: "http://my-service"
      outbound:
        - name: "internal-services"
          hosts: ["*.example.org"]
          outbound-token:
            header: "X-Internal-Auth"

How does it work?

Building OIDC configuration validates required client and identity values. An enabled OidcFeature also validates the configured signing-key route and fixed metadata or JWK values at startup. When using OidcProvider directly, these authentication-specific checks run when a tenant is first used for inbound authentication. Outbound-only token propagation and client-credentials exchange do not require inbound signing keys. Inline and classpath resources are fixed and are loaded when building the configuration.

Metadata and JWK sources that may become available later are loaded on the first authentication request that needs them. These include filesystem paths, URIs, OIDC discovery, and a remote JWK URI obtained from fixed metadata. Concurrent requests share the initial load rather than starting duplicate loads. Each attempt runs inside jwk-loader.timeout, attempts are grouped by jwk-loader.retry, and the complete retry batch runs inside jwk-loader.circuit-breaker.

If metadata or JWK loading remains temporarily unavailable, optional provider authentication abstains. Required provider authentication returns 401 Unauthorized with a WWW-Authenticate: Bearer challenge when header authentication is enabled, and 503 Service Unavailable otherwise. OIDC redirect callback and logout requests return 503 Service Unavailable for the same temporary source outage.

By default, an attempt times out after 5 seconds. At most two attempts are made, separated by a 200 ms delay, within an 11-second overall retry timeout. The 11-second budget accommodates both 5-second attempts and the delay. The circuit opens after the first exhausted batch, rejects requests for 5 seconds, and then permits a recovery probe. A successfully loaded value is cached for the life of that tenant configuration. All three policies use the standard Helidon Fault Tolerance configuration options under jwk-loader. Configuration is rejected at startup if the attempt timeout is not positive, exceeds the overall retry timeout, or does not use current-thread execution. Running the loader on the calling thread prevents a retry from overlapping a timed-out attempt that is still unwinding after interruption. The timeout interrupts the loader at its deadline; prompt termination also depends on the underlying I/O honoring interruption or enforcing its own timeout.

At runtime, depending on configuration...

If a request comes without a token or with insufficient scopes:

  1. If redirect is set to true (default), request is redirected to the authorization endpoint of the identity server. If set to false, 401 is returned
  2. User authenticates against the identity server
  3. The identity server redirects back to Helidon service with a code
  4. Helidon service contacts the identity server’s token endpoint, to exchange the code for a JWT
  5. The JWT is stored in a cookie (if cookie support is enabled, which it is by default)
  6. Helidon service redirects to original endpoint (on itself)

Redirect attempts are counted to prevent infinite login redirects. By default, Helidon stores the count in the redirect-attempt-param query parameter. Set redirect-attempt-counter-strategy to COOKIE to store the counter in a small cookie instead. Set it to NONE to disable redirect attempt counting and max-redirects loop protection. The redirect-attempt-param value is used as the cookie name prefix when the COOKIE strategy is used; the full cookie name also includes a tenant and original URI hash.

Some OIDC cookies are encrypted by default. For production deployments, configure cookie-encryption-password or cookie-encryption-name explicitly. The same secret or named encryption configuration must be available to every service instance that shares encrypted OIDC cookies.

If encrypted cookies are enabled and neither cookie-encryption-password nor cookie-encryption-name is configured, Helidon uses .helidon-oidc-secret in the current working directory as a local fallback secret file. When Helidon generates that fallback file, it logs a warning. This fallback is intended for a single service instance.

On POSIX file systems, Helidon creates the file with owner read/write permissions only and accepts an existing fallback file only when it is a regular file with owner-only read or read/write permissions. On non-POSIX file systems, Helidon still rejects symlinks and non-regular existing files, and creates the fallback file with an exclusive best-effort create operation.

For rolling upgrades from nodes that used the legacy password-based cookie encryption defaults, set legacy-cookie-encryption to true while both old and new nodes are running. This makes upgraded nodes keep writing cookies that older nodes can decrypt.

After all nodes run the new version, set legacy-cookie-encryption to false and set legacy-cookie-fallback to true for at least one cookie lifetime or session grace period. After legacy cookies have expired, set both flags to false. These flags are temporary compatibility controls for upgrades, not steady-state security settings.

These flags only affect password-based OIDC cookie encryption; named Security encryption configured with cookie-encryption-name uses its own encryption configuration.

For a top-level server-type=idcs, the access-token and ID-token cookies are GZIP-compressed by default when compression reduces their size. Compression is disabled by default for other server types and can be enabled explicitly with cookie-compression-enabled for the access token and cookie-compression-id-enabled for the ID token. Compression is applied before encryption when encryption is enabled, and uses a cookie-safe encoded form otherwise. Password-based legacy cookie encryption disables compression while legacy-cookie-encryption is true, even if the compression options are enabled; named Security encryption does not have this override. During a rolling upgrade from a version that does not understand compressed token cookies, set both options to false on all nodes before upgrading. New nodes still read compressed cookies while these settings are disabled, but write the older uncompressed format. Re-enable compression after all nodes have been upgraded and the rollback window has closed. Older nodes cannot read compressed cookies. Rolling back after compression has been re-enabled therefore requires invalidating affected sessions or waiting for the compressed cookies to expire and users to authenticate again.

Helidon obtains a token from request (from cookie, header, or query parameter):

  1. Token is parsed as a singed JWT
  2. We validate the JWT signature either against local JWK or against the identity server’s introspection endpoint depending on configuration
  3. We validate the issuer and audience of the token if it matches the configured values
  4. A subject is created from the JWT, including scopes from the token
  5. We validate that we have sufficient scopes to proceed, and return 403 if not
  6. Handling is returned to security to process other security providers

Multi Tenancy

The OIDC provider also supports multi tenancy. To enable this feature, it is required to do several steps.

  1. To enable the default multi-tenant support, add the multi-tenant: true option to the OIDC provider configuration
  2. Specify the desired way to provide the tenant name. This step is done over adding the tenant-id-style configuration option. For more information, see the table below
  3. Add the tenants section to the OIDC provider configuration
application.yaml
tenants:
   - name: "example-tenant"
     # ... tenant configuration options

There are four ways to provide the required tenant information to Helidon by default.

Possible tenant-id-style configuration options:

keydescriptionadditional config options
host-headerTenant configuration will be selected based on your host present in the Host header value.
domainSimilar to the host-header style, but now the tenant name is identified just as a part of the host name. By default, it selects the third domain level.

Example: Host header value from inbound request is my.helidon.com → domain level 3 is my, domain level 2 is helidon and domain level 1 is com.

tenant-id-domain-level: <domain level>
token-handlerThe tenant name information is expected to be provided through the configured custom header value.tenant-id-handler: header: "my-custom-header"
noneNo tenant name finding is used. Default tenant name @default is used instead.

You can also implement a custom way of discovering the tenant name and tenant configuration. The custom tenant name discovery from request can be done by implementing SPI:

io.helidon.security.providers.oidc.common.spi.TenantIdProvider

and the custom tenant configuration discovery can be provided by implementing SPI:

io.helidon.security.providers.oidc.common.spi.TenantConfigProvider

Available tenant config options

Configuration options

KeyTypeDefaultDescription
audienceStringAudience of issued tokens
authorization-endpoint-uriURIURI of an authorization endpoint used to redirect users to for logging-in
base-scopesStringopenidConfigure base scopes
check-audienceBooleantrueConfigure audience claim check
client-idStringClient ID as generated by OIDC server
client-secretStringClient secret as generated by OIDC server
client-timeout-millisDuration30000Timeout of calls using web client
decryption-keysConfiguration for decryption-keys
identity-uriURIURI of the identity server, base used to retrieve OIDC metadata
introspect-endpoint-uriURIEndpoint to use to validate JWT
issuerStringIssuer of issued tokens
jwk-loaderConfiguration for jwk-loader
nameStringName of the tenant
oidc-metadataConfiguration for oidc-metadata
oidc-metadata-well-knownBooleantrueIf set to true, metadata will be loaded from default (well known) location, unless it is explicitly defined using oidc-metadata-resource
optional-audienceBooleanfalseAllow audience claim to be optional
scope-audienceStringAudience of the scope required by this application
server-typeString@defaultConfigure one of the supported types of identity servers
sign-jwkConfiguration for sign-jwk
token-endpoint-authClientAuthenticationCLIENT_SECRET_BASICType of authentication to use when invoking the token endpoint. With CLIENT_SECRET_BASIC, credentials are sent only to POST requests on the resolved token endpoint scheme, host, and path and, when JWT introspection is used, to POST requests on the resolved introspection endpoint scheme, host, and path
token-endpoint-uriURIURI of a token endpoint used to obtain a JWT based on the authentication code
validate-jwt-with-jwkBooleantrueUse JWK (a set of keys to validate signatures of JWT) to validate tokens

How does that work?

Multi-tenant support requires to obtain tenant name from the incoming request. OIDC configuration is selected based on the received tenant name. The way this tenant name has to be provided is configured via tenant-id-style configuration. See How to enable tenants for more information. After matching tenant configuration with the received name, the rest of the OIDC flow if exactly the same as in How does OIDC work.

Base OIDC configuration is treated as a default tenant, which is used if no tenant name is provided. This default tenant has the name @default. An identified tenant name must match a configured tenant; unknown tenant names are rejected by default. Set fallback-to-default-tenant-enabled: true only when unknown tenant names should use the default tenant configuration.

It is also important to note, that each tenant configuration is based on the default tenant configuration (base OIDC configuration), and therefore its configuration do not need to change all the properties, if they do not differ from the base OIDC configuration.

CORS Settings

CORS is (now) a single component configured either through config (key cors), or programmatically via io.helidon.webserver.cors.CorsFeature. To add proper CORS setup for the OIDC endpoint, use one of these. Component specific CORS setup will be removed from Helidon.

HTTP Basic Authentication Provider

HTTP Basic authentication support for local testing and demos.

HTTP Basic authentication is not supported for production use.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-http-auth</artifactId>
</dependency>

Configuration options

KeyTypeDefaultDescription
outboundList<OutboundTarget>Add a new outbound target to configure identity propagation or explicit username/password
optionalBooleanfalseWhether authentication is required
realmStringhelidonSet the realm to use when challenging users
principal-typeSubjectTypeUSERPrincipal type this provider extracts (and also propagates)
usersList<ConfigUser>Set user store to validate users

Configuration Example

application.yaml
security:
  providers:
  - http-basic-auth:
      realm: "helidon"
      users:
      - login: "john"
        password: "${CLEAR=changeit}"
        roles: ["admin"]
      - login: "jack"
        password: "changeit"
        roles: ["user", "admin"]
      outbound:
        - name: "internal-services"
          hosts: ["*.example.org"]
          # Propagates current user's identity or identity from request property
          outbound-token:
            header: "X-Internal-Auth"
        - name: "partner-service"
          hosts: ["*.partner.org"]
          # Uses this username and password
          username: "partner-user-1"
          password: "${CLEAR=changeit}"

For all security providers, outbound target methods loaded from configuration temporarily match the exact configured token and its standard uppercase form when the token names a known method in lowercase or mixed case. For example, get matches get and GET, but does not match Get. Uppercase known methods and custom methods remain exact and case-sensitive. Programmatic builder method selectors remain exact.

Loading a non-uppercase known method logs a warning. Update ordinary method names to uppercase now: this compatibility will be removed in a future major version, when configuration will match only the exact token. See the upgrade guide for the known methods and the scope of this compatibility.

Example

See the example on GitHub.

How does it work?

See https://tools.ietf.org/html/rfc7617.

Authentication of request

When a request is received without the Authorization: basic ... header, a challenge is returned to provide such authentication.

When a request is received with the Authorization: basic ... header, the username and password is validated against configured users (and users obtained from custom service if any provided).

Subject is created based on the username and roles provided by the user store.

Identity propagation

When identity propagation is configured, there are several options for identifying username and password to propagate:

  1. We propagate the current username and password (inbound request must be authenticated using basic authentication).
  2. We use username and password from an explicitly configured property (See EndpointConfig.PROPERTY_OUTBOUND_ID and EndpointConfig.PROPERTY_OUTBOUND_SECRET)
  3. We use username and password associated with an outbound target (see example configuration above)

Identity is propagated only if:

  1. There is an outbound target configured for the endpoint
  2. Or there is an explicitly configured username/password for the current request (through request property)

Custom user store

Java service loader service io.helidon.security.providers.httpauth.spi.UserStoreService can be implemented to provide users to the provider, such as when validated against an internal database or LDAP server. The user store is defined so you never need the clear text password of the user.

Warning on security of HTTP Basic Authentication (or lack thereof)

Basic authentication uses base64 encoded username and password and passes it over the network. Base64 is only encoding, not encryption - so anybody that gets hold of the header value can learn the actual username and password of the user. This is a security risk and an attack vector that everybody should be aware of before using HTTP Basic Authentication. HTTP Basic authentication is not supported for production use. We recommend it only for local testing and demo purposes.

Header Authentication Provider

Asserts user or service identity based on a value of a header.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-header</artifactId>
</dependency>

Configuration options

KeyTypeDefaultDescription
atn-tokenTokenHandlerToken handler to extract username from request
authenticateBooleantrueWhether to authenticate requests
outboundList<OutboundTarget>Configure outbound target for identity propagation
propagateBooleanfalseWhether to propagate identity
optionalBooleanfalseWhether authentication is required
outbound-tokenTokenHandlerToken handler to create outbound headers to propagate identity
principal-typeSubjectTypeUSERPrincipal type this provider extracts (and also propagates)

Configuration Example

application.yaml
security:
  providers:
    header-atn:
      atn-token:
        header: "X-AUTH-USER"
      outbound:
        - name: "internal-services"
          hosts: ["*.example.org"]
          # propagates the current user or service id using the same header as authentication
        - name: "partner-service"
          hosts: ["*.partner.org"]
          # propagates an explicit username in a custom header
          username: "service-27"
          outbound-token:
            header: "X-Service-Auth"

How does it work?

This provider inspects a specified request header and extracts the username/service name from it and asserts it as current subject’s principal.

This can be used when we use perimeter authentication (e.g. there is a gateway that takes care of authentication and propagates the user in a header).

Identity propagation

Identity is propagated only if an outbound target matches the target service.

The following options exist when propagating identity: 1. We propagate the current username using the configured header 2. We use username associated with an outbound target (see example configuration above)

Caution

When using this provider, you must be sure the header cannot be explicitly configured by a user or another service. All requests should go through a gateway that removes this header from inbound traffic, and only configures it for authenticated users/services. Another option is to use this with fully trusted parties (such as services within a single company, on a single protected network not accessible to any users), and of course for testing and demo purposes.

HTTP Signatures Provider

Support for HTTP Signatures.

Inbound and outbound sign-headers method selectors loaded from configuration temporarily match the exact configured token and, for a non-uppercase known method, its standard uppercase form. For example, PoSt matches PoSt and POST, but does not match post. An explicit uppercase entry takes precedence over a compatibility entry regardless of configuration order. If several case variants imply the same uppercase entry and no explicit uppercase entry exists, the last configured variant supplies that uppercase entry's signed headers.

Loading a non-uppercase known method logs a warning. Update ordinary method names to uppercase now; compatibility will be removed in a future major version, when selectors will match only the exact configured token. Programmatic builder method selectors remain exact. Outbound target methods use the same configuration compatibility described in the upgrade guide.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-http-sign</artifactId>
</dependency>

Configuration options

KeyTypeDefaultDescription
headersList<HttpSignHeader>Add a header that is validated on inbound requests
inbound-date-validityDurationPT5MConfigure the maximum accepted age or future skew for the signed Date header
outboundOutboundConfigAdd outbound targets to this builder
inbound.keysList<InboundClientDefinition>Add inbound configuration
backward-compatible-eolBooleanfalseEnable support for Helidon versions before 3.0.0 (exclusive)
optionalBooleantrueSet whether the signature is optional
realmStringhelidonRealm to use for challenging inbound requests that do not have "Authorization" header in case header is HttpSignHeader#AUTHORIZATION and singatures are not optional
sign-headersList<HeadersConfig>Override the default inbound required headers (e.g

Configuration Example

application.yaml
security:
  providers:
    - http-signatures:
        inbound:
          keys:
            - key-id: "service1-hmac"
              principal-name: "Service1 - HMAC signature"
              hmac.secret: "${CLEAR=changeit}"
            - key-id: "service1-rsa"
              principal-name: "Service1 - RSA signature"
              public-key:
                keystore:
                  resource.path: "src/main/resources/keystore.p12"
                  passphrase: "changeit"
                  cert.alias: "service_cert"
        outbound:
          - name: "service2-hmac"
            hosts: ["localhost"]
            paths: ["/service2"]
            signature:
              key-id: "service1-hmac"
              hmac.secret: "${CLEAR=changeit}"
          - name: "service2-rsa"
            hosts: ["localhost"]
            paths: ["/service2-rsa.*"]
            signature:
              key-id: "service1-rsa"
              private-key:
                keystore:
                  resource.path: "src/main/resources/keystore.p12"
                  passphrase: "changeit"
                  key.alias: "myPrivateKey"

Example

See the example on GitHub.

Signature basics

  • standard: based on https://tools.ietf.org/html/draft-cavage-http-signatures-03
  • key-id: an arbitrary string used to locate signature configuration - when a request is received the provider locates validation configuration based on this id (e.g. HMAC shared secret or RSA public key). Commonly used meanings are: key fingerprint (RSA); API Key

How does it work?

Inbound Signatures We act as a server and another party is calling us with a signed HTTP request. We validate the signature and assume identity of the caller.

By default, inbound validation requires signed date, (request-target), and host fields. The authorization field must also be signed when it is present, unless the signature itself is carried in the Authorization header. The signed Date value must be within PT5M of the server time; configure inbound-date-validity to another duration, or to PT0S to disable date freshness validation. Date freshness rejects stale or far-future signatures; it does not provide nonce-based replay detection within the accepted time window. The (request-target) field uses the lower-case HTTP method followed by a space, the request path, and the raw query string from the security environment, when present. Query parameter order and encoding are significant.

The provider implements draft-cavage-http-signatures-03, which requires that lowercase representation. Consequently, (request-target) does not bind the original method case: methods such as GET, get, and GeT produce the same signed method text. Do not rely on this legacy signed component when a trust boundary must distinguish method case.

Use sign-headers to require additional signed fields such as digest, content-length, or content-type for selected methods. Configured method selectors use the temporary compatibility described above; this does not change (request-target) canonicalization.

If a request carries the signature in the Authorization header, that header value cannot be combined with any other authorization scheme. Use the standalone Signature header when another Authorization value must be sent with the same request.

Outbound Signatures We act as a client and we sign our outgoing requests. If there is a matching outbound target specified in configuration, its configuration will be applied for signing the outgoing request, otherwise there is no signature added

By default, outbound signing includes date, (request-target), and host. It also signs authorization when that field is present, unless the signature itself is carried in the Authorization header. The provider adds date and host when they are required but missing.

ABAC Provider

Attribute based access control authorization provider.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-abac</artifactId>
</dependency>

Configuration options

KeyTypeDefaultDescription
fail-if-none-validatedBooleantrueWhether to fail if NONE of the attributes is validated
fail-on-unvalidatedBooleantrueWhether to fail if any attribute is left unvalidated

Configuration Example

application.yaml
security:
  providers:
    - abac:

How does it work?

ABAC uses available validators and validates them against attributes of the authenticated user.

Combinations of fail-on-unvalidated and fail-if-none-validated:

  1. true & true: Will fail if any attribute is not validated and if any has failed validation
  2. false & true: Will fail if there is one or more attributes present and NONE of them is validated or if any has failed validation, Will NOT fail if there is at least one validated attribute and any number of not validated attributes (and NONE failed)
  3. false & false: Will fail if there is any attribute that failed validation, Will NOT fail if there are no failed validation or if there are NONE validated

Any attribute of the following objects can be used:

  • environment (such as time of request) - e.g. env.time.year
  • subject (user) - e.g. subject.principal.id
  • subject (service) - e.g. service.principal.id
  • object (must be explicitly invoked by developer in code, as object cannot be automatically added to security context) - e.g. object.owner

This provider checks that all defined ABAC validators are validated. If there is a definition for a validator that is not checked, the request is denied (depending on configuration as mentioned above).

ABAC provider also allows an object to be used in authorization process, such as when evaluating if an object’s owner is the current user. The following example uses the Expression language validator to demonstrate the point in an endpoint:

Example of using an object

AbacResource.java
@Authenticated
@Http.Path("/abac")
public class AbacEndpoint {
    @Http.GET
    @Authorized(explicit = true)
    @PolicyStatement("${env.time.year >= 2017 && object.owner == subject.principal.id}")
    public String process(SecurityContext context) {
        // probably looked up from a database
        SomeResource res = new SomeResource("user");
        AuthorizationResponse atzResponse = context.authorize(res);

        if (atzResponse.isPermitted()) {
            return "fine, sir";
        }
        return atzResponse.description().orElse("Access not granted");
    }
}

The following validators are implemented:

Role Validator

Checks whether user/service is in either of the required role(s).

Configuration Key: role-validator

Annotations: @RolesAllowed, @RoleValidator.Roles

Configuration example for WebServer

application.yaml
security:
  web-server.paths:
    - path: "/user/*"
      roles-allowed: ["user"]

Annotation example

@RolesAllowed("user")
@RoleValidator.Roles(value = "service_role", subjectType = SubjectType.SERVICE)
@Authenticated
@Http.Path("/abac")
public class AbacEndpoint {
}

Scope Validator

Checks whether user has all the required scopes.

Configuration Key: scope-validator

Annotations: @Scope

Configuration example for WebServer

application.yaml
security:
  web-server.paths:
    - path: "/user/*"
      abac.scopes:
        ["calendar_read", "calendar_edit"]

Annotation example

@Scope("calendar_read")
@Scope("calendar_edit")
@Authenticated
@Http.Path("/abac")
public class AbacEndpoint {
}

Expression Language Policy Validator

Policy executor using Java EE policy expression language (EL)

Configuration Key: policy-javax-el

Annotations: @PolicyStatement

Example of a policy statement: ${env.time.year >= 2017}

Configuration example for WebServer

application.yaml
security:
  web-server.paths:
    - path: "/user/*"
      policy:
        statement: "hasScopes('calendar_read','calendar_edit') AND timeOfDayBetween('8:15', '17:30')"

Annotation example

@PolicyStatement("${env.time.year >= 2017}")
@Authenticated
@Http.Path("/abac")
public class AbacEndpoint {
}

Configuration example for endpoint security over configuration

application.yaml
server:
  features:
    security:
      endpoints:
        - path: "/somePath"
          config:
            abac.policy-validator.statement: "\\${env.time.year >= 2017}"

JWT Provider

JWT token authentication and outbound security provider.

Maven Coordinates

pom.xml
<dependency>
  <groupId>io.helidon.security.providers</groupId>
  <artifactId>helidon-security-providers-jwt</artifactId>
</dependency>

Configuration options

KeyTypeDefaultDescription
allow-impersonationBooleanfalseWhether to allow impersonation by explicitly overriding username from outbound requests using io.helidon.security.EndpointConfig#PROPERTY_OUTBOUND_ID property
allow-unsignedBooleanfalseConfigure support for unsigned JWTs without requiring verification JWKs
atn-tokenConfiguration for atn-token
authenticateBooleantrueWhether to authenticate requests
jwk-loaderConfiguration for jwk-loader
jwt-groups-pathStringgroupsPath to the JWT payload claim containing the groups to add as role grants
jwt-groups-separatorStringSeparator used to split a string claim value into multiple groups
optionalBooleanfalseWhether authentication is required
principal-typeSubjectTypeUSERPrincipal type this provider extracts (and also propagates)
propagateBooleantrueWhether to propagate identity
sign-tokenOutboundConfigConfiguration of outbound rules
use-jwt-groupsBooleantrueClaim groups from JWT will be used to automatically add groups to current subject (may be used with jakarta.annotation.security.RolesAllowed annotation)

Configuration Example

application.yaml
security:
  providers:
    - provider:
        atn-token:
          jwk.resource.resource-path: "verifying-jwk.json"
          jwt-issuer: "http://trusted.issuer"
          jwt-audience: "http://my.service"
        sign-token:
          jwk.resource.resource-path: "signing-jwk.json"
          jwt-issuer: "http://my.server/identity"
          outbound:
          - name: "propagate-token"
            hosts: ["*.internal.org"]
          - name: "generate-token"
            hosts: ["1.partner-service"]
            jwk-kid: "partner-1"
            jwt-kid: "helidon"
            jwt-audience: "http://1.partner-service"

Example

See the example on GitHub.

How does it work?

JSON Web Token (JWT) provider has support for authentication and outbound security.

Authentication is based on validating the token (signature, valid before etc.) and on asserting the subject of the JWT subject claim.

Inline and classpath verification JWK resources are loaded and validated when the provider starts. Filesystem and URI resources are loaded on the first authentication request because they may become available later. Concurrent requests share that initial load. Each attempt has a default 5-second timeout; at most two attempts are made, separated by 200 ms, within an 11-second overall retry timeout. After the first exhausted batch, the circuit breaker rejects loads for 5 seconds before permitting a recovery probe. A successful key set is cached for the life of the provider. Configure the standard Timeout, Retry, and Circuit Breaker options under jwk-loader.timeout, jwk-loader.retry, and jwk-loader.circuit-breaker, respectively. The 11-second budget accommodates both 5-second attempts and the retry delay. Configuration is rejected at startup if the attempt timeout is not positive, exceeds the overall retry timeout, or does not use current-thread execution. This prevents a retry from overlapping a timed-out loader that is still unwinding after interruption. The deadline interrupts the loader, so prompt termination also depends on the underlying I/O honoring interruption or enforcing its own timeout.

If verification-key loading remains temporarily unavailable, optional authentication abstains. Required authentication using the built-in Authorization Bearer token handler returns 401 Unauthorized with a WWW-Authenticate: Bearer challenge. A provider using a custom token handler returns 503 Service Unavailable instead.

If allow-unsigned is explicitly enabled, a token using the none algorithm without a key ID does not require or trigger loading of verification JWKs. Signed tokens still require matching verification keys. Enabling unsigned tokens is dangerous and should be limited to environments where untrusted parties cannot supply JWTs.

For outbound, we support either token propagation (e.g. the token from request is propagated further) or support for generating a brand-new token based on configuration of this provider.

Reference

Copyright © 2018, 2026 Oracle and/or its affiliates.