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
verikloakat runtime and pairs well withverikloak-railsfor Rails integrations. - Provides a
pundit_userhook 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_userprovider for Rails controllers -
Generator:
rails g verikloak:pundit:installcreates initializer + policy template (withhas_permission?support for realm roles plus the configured resource scope)
Installation
bundle add verikloak-punditIf you're on Rails:
rails g verikloak:pundit:installThis 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!
endYour policy can then do:
class NotePolicy < ApplicationPolicy
def update?
user.has_role?(:admin) || user.resource_role?(:'rails-api', :editor)
end
endWhere 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
endWorking 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-userheader). Make sure your Rack stack stores the decoded claims under the sameenv_claims_keyconfigured 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_clientsto 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_mapkeys with that naming convention souser.has_permission?resolves correctly. If Audience adds its own client entry insideresource_access, add that client id topermission_resource_clientswhen 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 rolesTesting
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 -aWhen 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! }
endAn 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_resourcespulls 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_resourceswithpermission_resource_clientsto 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 = trueexposesverikloak_claimsto the Rails view layer. If the claims include personal or sensitive data, consider switching it tofalseand 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