verikloak-audience
Rack middleware for validating the aud claim of Keycloak-issued tokens on top of the Verikloak stack. It ships with deploy-friendly presets that address common Keycloak patterns such as account co-existence and resource_access-driven role enforcement.
For the full error behaviour (response shapes, exception classes, logging hints), see ERRORS.md.
Insert the middleware immediately after
Verikloak::Middleware. Doing so ensures the token is already verified and the claims are available viaenv["verikloak.user"](default).
Why
- Keycloak often emits multiple audiences (e.g.
["rails-api","account"]) - Some deployments primarily rely on
resource_access[client].roles - Re-implementing permissive/strict
audchecks per app is a maintenance burden
Profiles
| Profile | Summary | Suggested scenarios / when suggest_in_logs points here |
|---|---|---|
:strict_single (recommended)
|
Requires aud to match required_aud exactly (order-insensitive, no extras). |
APIs where audiences are cleanly separated. Logged suggestion when the observed aud already equals the configured list. |
:allow_account |
Allows account in addition to required audiences (e.g. ["rails-api","account"]). |
SPA + API mixes where Keycloak always emits account. Suggested when strict mode fails and the log shows profile=:allow_account. |
:any_match |
Passes when at least one of the required audiences is present. | Shared APIs where multiple clients may have overlapping audiences. More permissive than :strict_single. |
:resource_or_aud |
Passes when resource_access[client].roles is present; otherwise falls back to :allow_account. |
Services relying on resource roles. Suggested when logs output profile=:resource_or_aud. |
Installation
bundle add verikloak-audienceIn Rails applications, run the generator to create a configuration initializer:
rails g verikloak:audience:installThis creates config/initializers/verikloak_audience.rb with configuration options.
The middleware is automatically inserted by the Railtie after Verikloak::Middleware.
Manual Rack / Rails setup
Alternatively, you can manually insert after Verikloak::Middleware:
# config/application.rb
config.middleware.insert_after Verikloak::Middleware, Verikloak::Audience::Middleware,
profile: :allow_account,
required_aud: ["rails-api"],
resource_client: "rails-api",
env_claims_key: "verikloak.user",
suggest_in_logs: trueSee examples/rack.ru for a full Rack sample. In Rails, always insert immediately after the core middleware; otherwise env['verikloak.user'] will be empty and every request will fail with 403.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
profile |
Symbol | :strict_single |
Profile selector. Accepts :strict_single, :allow_account, :any_match, or :resource_or_aud. |
required_aud |
Array/String/Symbol | [] |
Required audience values; coerced to an array internally. |
resource_client |
String | "rails-api" |
Keycloak client id used to look up resource_access[client].roles. |
env_claims_key |
String | "verikloak.user" |
Rack env key where verified claims are stored. |
suggest_in_logs |
Boolean | true |
Emits a WARN log with the suggested profile when validation fails. |
env_claims_key assumes the preceding Verikloak::Middleware populates the Rack env. If the middleware order changes, claims will be missing and the audience check will always reject.
Operational safeguards
- Middleware initialisation now fails fast when
required_audis empty. When Rails loads via the supplied Railtie,Verikloak::Audience.config.validate!runs after boot so configuration mistakes surface during startup instead of returning 403 for every request. - When audience validation fails, the middleware consults
env['verikloak.logger'],env['rack.logger'], andenv['action_dispatch.logger'](in that order) before falling back to Ruby'sKernel#warn, keeping failure logs consistent with Rails and Verikloak observers. - For the
:resource_or_audprofile,resource_clientmust match one of the values inrequired_aud. A single-elementrequired_audautomatically infers the client id, ensuring the same client identifier is shared with downstream BFF/Pundit integrations.
Keycloak Integration
Default Audience Behavior
Keycloak access tokens include aud: "account" by default. This is often unexpected for developers who expect the client ID to appear in the audience claim.
| Token Type | Default aud Value |
|---|---|
| Access Token | "account" |
| ID Token | Client ID |
Common Issue
# Configuration
config.verikloak.audience = 'rails-api'
# Actual token payload
{
"aud": "account", # NOT "rails-api"!
"sub": "user-123",
...
}
# Result: 403 Forbidden - audience validation failsSolution: Configure Audience Mapper in Keycloak
To add a custom audience to access tokens:
- Go to Clients → Select your client (e.g.,
rails-api) - Navigate to Client scopes tab
- Click on the dedicated scope (e.g.,
rails-api-dedicated) - Go to Mappers tab → Add mapper → By configuration
- Select Audience
- Configure:
-
Name:
rails-api-audience -
Included Client Audience:
rails-api - Add to access token: ON
-
Name:
- Save
After this configuration, tokens will include:
{
"aud": ["rails-api", "account"],
...
}Configuration Examples
Option 1: Allow both custom and account audience
# config/initializers/verikloak.rb
Rails.application.configure do
config.verikloak.audience = ['rails-api', 'account']
endOption 2: Use :allow_account profile
# With verikloak-audience middleware
use Verikloak::Audience::Middleware,
profile: :allow_account,
required_aud: ['rails-api']Option 3: Strict single audience (requires Keycloak Mapper)
# Only works if Keycloak Audience Mapper is configured
use Verikloak::Audience::Middleware,
profile: :strict_single,
required_aud: ['rails-api']Troubleshooting
"Audience validation failed" errors
-
Check your token's
audclaim:# Decode JWT (paste your token) echo "YOUR_TOKEN" | cut -d. -f2 | base64 -d | jq .aud
-
If
audis only"account":- Add an Audience Mapper in Keycloak (see above)
- OR use
:allow_accountprofile
-
If using oauth2-proxy:
- Ensure the correct client ID is configured
- Check that the token is being forwarded correctly
Testing
All pull requests and pushes are automatically tested with RSpec and RuboCop via GitHub Actions. See the CI badge at the top for current build status.
To run the test suite locally:
docker compose run --rm dev rspec
docker compose run --rm dev rubocop -aContributing
Bug reports and pull requests are welcome! Please see CONTRIBUTING.md for details.
Security
If you find a security vulnerability, please follow the instructions in SECURITY.md.
License
This project is licensed under the MIT License.
Publishing (for maintainers)
Gem release instructions are documented separately in MAINTAINERS.md.
Changelog
See CHANGELOG.md for release history.
References
- Verikloak (core): https://github.com/taiyaky/verikloak
- verikloak-rails (Rails integration): https://github.com/taiyaky/verikloak-rails
- verikloak-audience on RubyGems: https://rubygems.org/gems/verikloak-audience