verikloak-pundit
Pundit integration for the Verikloak family. This gem maps Keycloak roles from JWT claims (e.g., realm_access.roles
, resource_access[client].roles
) into a convenient UserContext that Pundit policies can consume.
- Requires
verikloak
at runtime and pairs well withverikloak-rails
for Rails integrations. - Provides a
pundit_user
hook so policies can useuser.has_role?(:admin)
etc. - Keeps role mapping configurable (project-specific mappings differ).
Features
- UserContext: lightweight wrapper around JWT claims
-
Delegations:
has_role?
,in_group?
,resource_role?(client, role)
helpers for controllers and policies - RoleMapper: optional map from Keycloak roles → domain permissions
-
Controller integration:
pundit_user
provider for Rails controllers -
Generator:
rails g verikloak:pundit:install
creates initializer + policy template (withhas_permission?
support for realm roles plus the configured resource scope)
Installation
bundle add verikloak-pundit
If you're on Rails:
rails g verikloak:pundit:install
This generates:
config/initializers/verikloak_pundit.rb
-
app/policies/application_policy.rb
(template if missing; optional)
For error-handling guidance, see ERRORS.md.
Quick Start (Rails)
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include Pundit
include Verikloak::Pundit::Controller # provides pundit_user
# If you're also using verikloak-rails:
# before_action :authenticate_user!
end
Your policy can then do:
class NotePolicy < ApplicationPolicy
def update?
user.has_role?(:admin) || user.resource_role?(:'rails-api', :editor)
end
end
Where user
is the UserContext provided by pundit_user
.
Configuration
# config/initializers/verikloak_pundit.rb
Verikloak::Pundit.configure do |c|
c.resource_client = "rails-api" # default client for resource roles
c.role_map = { # optional role → permission mapping
admin: :manage_all,
editor: :write_notes,
reader: :read_notes
}
# Where to find claims in Rack env (when using verikloak/verikloak-rails)
c.env_claims_key = "verikloak.user"
# How to traverse JWT for roles
c.realm_roles_path = %w[realm_access roles] # => claims["realm_access"]["roles"]
# Lambdas in the path may accept (cfg) or (cfg, client)
# where `client` is the argument passed to `user.resource_roles(client)`
c.resource_roles_path = ["resource_access", ->(cfg){ cfg.resource_client }, "roles"]
# Permission mapping scope for `user.has_permission?`:
# :default_resource => realm roles + default client roles (recommended)
# :all_resources => realm roles + roles from all clients in resource_access
# (enabling this broadens permissions to every resource client;
# review the upstream role assignments before turning it on)
c.permission_role_scope = :default_resource
# Optional whitelist of resource clients when `permission_role_scope = :all_resources`.
# Leaving this as nil keeps the legacy "all clients" behavior, while providing
# an explicit list (e.g., %w[rails-api verikloak-bff]) limits which clients can
# contribute roles to permission checks.
c.permission_resource_clients = nil
# Expose `verikloak_claims` to views via helper_method (Rails only)
c.expose_helper_method = true
end
Working with other Verikloak gems
-
verikloak-bff: When your Rails application sits behind the BFF, the access
token presented to verikloak-pundit typically originates from the BFF
(e.g. via the
x-verikloak-user
header). Make sure your Rack stack stores the decoded claims under the sameenv_claims_key
configured above (the default"verikloak.user"
works out of the box withverikloak-bff >= 0.3
). If the BFF issues tokens for multiple downstream services, setpermission_resource_clients
to the limited list of clients whose roles should affect Rails-side authorization to avoid accidentally inheriting permissions meant for other services. -
verikloak-audience: Audience services often mint resource roles with a
service-specific prefix (for example,
audience-service:editor
). Align yourrole_map
keys with that naming convention souser.has_permission?
resolves correctly. If Audience adds its own client entry insideresource_access
, add that client id topermission_resource_clients
when you need to consume those roles from Rails.
Non-Rails / custom usage
claims = { "sub" => "123", "email" => "a@b", "realm_access" => {"roles" => ["admin"]} }
ctx = Verikloak::Pundit::UserContext.new(claims, resource_client: "rails-api")
ctx.has_role?(:admin) # => true
ctx.resource_role?(:"rails-api", :writer) # depends on resource_access
ctx.has_permission?(:manage_all) # from role_map, realm or resource roles
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 -a
When writing specs, call Verikloak::Pundit.reset!
in your test teardown to ensure configuration changes do not leak between examples:
RSpec.configure do |config|
config.after { Verikloak::Pundit.reset! }
end
An additional integration check exercises the gem together with the latest verikloak
and verikloak-rails
releases. This runs in CI automatically, and you can execute it locally with:
docker compose run --rm -e BUNDLE_FROZEN=0 dev bash -lc '
cd integration && \
apk add --no-cache --virtual .integration-build-deps \
build-base \
linux-headers \
openssl-dev \
yaml-dev && \
bundle config set --local path vendor/bundle && \
bundle install --jobs 4 --retry 3 && \
bundle exec ruby check.rb && \
apk del .integration-build-deps
'
Contributing
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.
Operational guidance
- Enabling
permission_role_scope = :all_resources
pulls roles from every Keycloak client inresource_access
. Review the granted roles carefully to ensure you are not expanding permissions beyond what the application expects. - Combine
permission_role_scope = :all_resources
withpermission_resource_clients
to explicitly opt-in the clients that may contribute permissions. Leaving the whitelist blank (the default) reverts to the legacy behavior of trusting every client in the token. - Leaving
expose_helper_method = true
exposesverikloak_claims
to the Rails view layer. If the claims include personal or sensitive data, consider switching it tofalse
and pass only the minimum required information through controller-provided helpers.
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-pundit on RubyGems: https://rubygems.org/gems/verikloak-pundit