Skip to content
Menu
DevSecOps Now!!!
  • About
  • Certifications
  • Contact
  • Courses
  • DevSecOps Consulting
  • DevSecOps Tools
  • Training
  • Tutorials
DevSecOps Now!!!

Adding Group Membership Claims in Keycloak for wizbrand

Posted on September 24, 2025

Limited Time Offer!

For Less Than the Cost of a Starbucks Coffee, Access All DevOpsSchool Videos on YouTube Unlimitedly.
Master DevOps, SRE, DevSecOps Skills!

Enroll Now

This guide explains how to configure Keycloak so that your Laravel app (wizbrand-web) receives organization and role membership in the user’s token.

We’ll use Groups to represent organizations and their roles, then expose them via a Group Membership mapper.


Why Groups?

In wizbrand, a user can belong to multiple organizations with different roles (Admin, Manager, User).
For example:

  • Ashwani is Admin in Org 123
  • Ashwani is Manager in Org 456

We want Keycloak tokens to reflect this like:

"groups": [
  "/org-123/admin",
  "/org-456/manager"
  "/org-346/user"
]

Your Laravel app can then enforce access easily by parsing the groups claim.


Step 1 — Create a Client Scope with Group Membership

Instead of configuring mappers directly on the client, the recommended approach is to use Client Scopes (reusable across clients).

  1. Go to Client Scopes
    • Left sidebar → Client scopes → Create client scope.
  2. Fill details
    • Name: wizbrand-groups
    • Type: Default
    • Protocol: openid-connect
    • Click Save.
  1. Add Group Membership mapper
    • Inside wizbrand-groups → Mappers tab → Add mapper.
    • Mapper Type: Group Membership
    • Name: groups
    • Token Claim Name: groups
    • Full group path: ON
    • Add to ID token: ON
    • Add to Access token: ON
    • (Optional) Add to Userinfo: ON (useful for debugging)
    • Click Save.

This ensures every token will carry group memberships like /org-123/admin.


Step 2 — Attach the Client Scope to wizbrand-web

Now attach the scope to your Laravel client:

  1. Navigate to Clients
    • Left sidebar → Clients → select wizbrand-web.
  2. Open Client Scopes tab
    • Under Assigned Default Client Scopes, click Add client scope.
    • Select wizbrand-groups.
    • Assign as Default.

Now, every login via wizbrand-web automatically includes the groups claim.


Step 3 — Model Organizations with Groups

We’ll use groups in Keycloak to represent organizations and roles.

  1. Create parent group for each organization
    • Left sidebar → Groups → New.
    • Example: /org-123
  2. Add subgroups for roles
    • Inside /org-123, create:
      • /org-123/admin
      • /org-123/manager
      • /org-123/user
  3. Assign users to the correct subgroup
    • Example:
      • Ashwani → /org-123/admin
      • Priya → /org-123/manager

Later, you can automate this with the Keycloak Admin REST API.


Step 4 — Verify with Token Evaluation

  1. Go to:
    Clients → wizbrand-web → Client scopes → Evaluate.
  2. Choose a test user who belongs to a group (e.g., /professwiz/admin).
  3. Click Generate token → Decode the JSON.

You should see output like:

{
  "exp": 1758707369,
  "iat": 1758707069,
  "iss": "http://localhost:8080/realms/wizbrand",
  "aud": ["realm-management", "account"],
  "azp": "wizbrand-web",
  "name": "Ashwanik Kumar",
  "preferred_username": "ashwanik",
  "email": "ashwanik@ashwanik.com",
  "groups": [
    "/professwiz/admin"
  ]
}

✅ groups claim is present and shows full path.

Mental model (single, consistent source of truth)

  • Organizations are groups
    Each org is a parent group in Keycloak: /org-{ORG_ID} (e.g., /org-123).
  • Roles are subgroups
    Under each org group, create fixed subgroups:
    /org-123/admin, /org-123/manager, /org-123/user.
  • Membership = authorization
    Put a user into one or more of those subgroups. That’s your authorization state.
  • Tokens carry memberships
    The Group Membership protocol mapper projects these into the user’s tokens as: "groups": [ "/org-123/admin", "/org-456/manager" ]
  • Laravel enforces
    Your app reads groups, derives (orgId, role) pairs, and gates UI + API access accordingly.

Result: No scattered role flags, no per-app duplication. Keycloak is the single source of truth for org/role membership.


Why “full group path = ON” is crucial

Turning on Full group path ensures you don’t just get admin, but the org context, e.g. /org-123/admin.
This solves two hard problems in one go:

  1. Multi-org users: You can tell which org a role belongs to.
  2. Name collisions: Every org has an admin subgroup—paths keep them distinct.

Role semantics (keep it simple & predictable)

Define a clear, system-wide convention:

  • Admin: full CRUD on org resources; can invite/promote/demote members.
  • Manager: read + update on org resources; cannot manage members (or limit to specific domains, e.g., SEO panel).
  • User: read-only.

Tip: Keep the ladder strictly increasing (User ⟶ Manager ⟶ Admin). This makes role precedence trivial: if a user is both /org-123/user and /org-123/admin, treat them as Admin for org-123.


Parsing the groups claim (tiny, reliable helper)

You’ll see entries like:
/org-123/admin, /org-15/manager, /org-77/user

A tiny parser turns those into a map you can use everywhere.

Regex: ^/org-(\d+)/(admin|manager|user)$

PHP helper (pure function):

function parseOrgRolesFromGroups(array $groups): array {
    $orgRoles = []; // [orgId => highestRole]
    foreach ($groups as $g) {
        if (preg_match('#^/org-(\d+)/(admin|manager|user)$#', $g, $m)) {
            $orgId = (int)$m[1];
            $role  = $m[2];
            // precedence: admin > manager > user
            $rank = ['user'=>1, 'manager'=>2, 'admin'=>3];
            if (!isset($orgRoles[$orgId]) || $rank[$role] > $rank[$orgRoles[$orgId]]) {
                $orgRoles[$orgId] = $role;
            }
        }
    }
    return $orgRoles; // e.g., [123=>'admin', 456=>'manager']
}

Why this matters:

  • Works for any number of orgs.
  • Enforces role precedence deterministically.
  • Keeps controller/middleware logic clean.

Laravel integration (end-to-end)

A) Capture tokens at login (Socialite)

You already get tokens from the Socialite Keycloak driver:

$kcUser       = Socialite::driver('keycloak')->user();
$accessToken  = $kcUser->token;
$idToken      = $kcUser->accessTokenResponseBody['id_token'] ?? null;
$refreshToken = $kcUser->refreshToken ?? null;

// Store minimally & safely (session or encrypted store)
session([
  'kc_access_token'  => $accessToken,
  'kc_id_token'      => $idToken,
  'kc_refresh_token' => $refreshToken,
]);

Prefer using the ID token to read groups (it’s meant for the client).
For backend APIs, validate Access token on each call if needed.

B) Decode token & cache groups

Use a lightweight JWT decode (no network) to read claims. You don’t need to verify the signature for UI decisions if you trust your session boundary—but for API/critical actions, validate the token or call introspection.

function readGroupsFromIdToken(?string $idToken): array {
    if (!$idToken) return [];
    $parts = explode('.', $idToken);
    if (count($parts) !== 3) return [];
    $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')), true);
    return $payload['groups'] ?? [];
}

Then parse & cache:

$groups  = readGroupsFromIdToken(session('kc_id_token'));
$orgMap  = parseOrgRolesFromGroups($groups);

// Cache for a short time to avoid re-parsing
cache()->put('orgRoles:'.auth()->id(), $orgMap, now()->addMinutes(10));

C) Middleware to guard org routes

Create EnsureOrgRole.php:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EnsureOrgRole
{
    public function handle(Request $request, Closure $next, string $minRole)
    {
        $orgId = (int) $request->route('orgId'); // or from header/body, but prefer route param
        $orgRoles = cache()->remember('orgRoles:'.auth()->id(), 600, function () {
            $groups = readGroupsFromIdToken(session('kc_id_token'));
            return parseOrgRolesFromGroups($groups);
        });

        if (!isset($orgRoles[$orgId])) {
            abort(403, 'Not a member of this organization.');
        }

        $rank = ['user'=>1, 'manager'=>2, 'admin'=>3];
        $actual = $orgRoles[$orgId];

        if ($rank[$actual] < $rank[$minRole]) {
            abort(403, "Requires role {$minRole} or higher.");
        }

        // Optionally, attach resolved role for downstream use
        $request->attributes->set('orgRole', $actual);

        return $next($request);
    }
}

Register middleware alias in app/Http/Kernel.php:

'org.role' => \App\Http\Middleware\EnsureOrgRole::class,

Use it in routes:

Route::middleware(['auth', 'org.role:manager'])
     ->get('/orgs/{orgId}/projects', [ProjectController::class, 'index']);

Route::middleware(['auth', 'org.role:admin'])
     ->post('/orgs/{orgId}/members', [MemberController::class, 'invite']);

UI can also read orgRole from the request to enable/disable buttons, tabs, and actions.

D) Policies (optional, nice for model-level checks)

public function update(User $user, Project $project)
{
    $orgId   = $project->org_id;
    $orgRole = request()->attributes->get('orgRole') 
               ?? parseOrgRolesFromGroups(readGroupsFromIdToken(session('kc_id_token')))[$orgId] ?? null;

    return in_array($orgRole, ['manager','admin'], true);
}

Multitenancy hardening (don’t trust client input)

  • Never trust orgId sent by the client to decide access. Always cross-check with the groups claim.
  • For data queries, filter by orgId after authorization (e.g., where('org_id', $orgId)), and never leak cross-org records.
  • If you pass tokens to microservices, have those services re-validate the Access token (either via JWKS or Keycloak introspection).

Lifecycle: invites, promotions, removals

  • Invite: On accept, add user to /org-123/user.
  • Promote: Move /org-123/user → /org-123/manager (or add both; the precedence logic still works).
  • Remove: Remove all /org-123/* for that user.

Changes reflect in new tokens. If you need near-instant enforcement, use short token TTL (e.g., 5–15 min) and refresh on sensitive screens; or call token introspection on critical actions.


Admin API snippets (automation-friendly)

Create org + role subgroups

# Get token for admin user (confidential client flow or direct access grant for tooling)
KC_BASE="https://kc.example.com/realms/wizbrand"
TOKEN=$(curl -s -X POST "$KC_BASE/protocol/openid-connect/token" \
  -d grant_type=password -d client_id=admin-cli -d username=admin -d password=... \
  | jq -r .access_token)

# Create /org-123
ORG_JSON='{"name":"org-123"}'
ORG_ID=$(curl -s -D - -o /dev/null -X POST "https://kc.example.com/admin/realms/wizbrand/groups" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "$ORG_JSON" | awk -F/ '/Location:/ {print $NF}')

# Create subgroups
for r in admin manager user; do
  curl -s -X POST "https://kc.example.com/admin/realms/wizbrand/groups/$ORG_ID/children" \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d "{\"name\":\"$r\"}"
done

Add a user to /org-123/admin

  1. Find subgroup ID for admin under org-123.
  2. PUT /admin/realms/{realm}/users/{userId}/groups/{groupId} with an empty body.

You can wrap this in your Org Service so developers never touch raw Keycloak APIs.


Performance & token size

  • A user in hundreds of orgs means many groups entries. That increases token size.
  • Mitigations:
    • Keep names compact (/org-123/admin is good).
    • Use Access tokens for API auth, store a compact org role map server-side (Redis) after first parse.
    • Consider “lazy” authorization for non-critical pages (resolve on demand).

Security checklist

  • Confidential client for server-side apps; keep client secret safe.
  • Short token TTL + refresh tokens for better revocation latency.
  • Enable HTTPS everywhere (no mixed content).
  • Validate JWT signatures on backend services (JWKS).
  • Rate-limit sensitive endpoints (invites, promotions).
  • Log who changed which membership and when.

Testing matrix (copy/paste into QA doc)

  • User in one org as user → can view, cannot update.
  • User promoted to manager → can update, cannot manage members.
  • User promoted to admin → full CRUD + member management.
  • User in two orgs (admin in 123, user in 456) → admin powers only in 123.
  • User removed from org → loses access after token refresh / re-login.
  • Token expires while active → refresh works, groups remain consistent.
  • API call with tampered orgId → blocked by middleware.
  • Very large memberships → app still responsive (caching is effective).

Common pitfalls & fixes

  • “Groups not in token”
    Ensure the Client Scope with the Group Membership mapper is attached as Default to wizbrand-web.
  • “Only role names, no org path”
    You forgot Full group path: ON in the mapper.
  • “Change in KC not reflected”
    Token still valid. Use shorter TTL, or refresh token after admin changes.
  • “Got redirected to master realm”
    Double-check Socialite Keycloak config: realm must be wizbrand and the issuer/redirect URLs must point to the right realm paths.

Putting it all together (the 5-step recipe)

  1. Keycloak groups: /org-{id} → admin|manager|user.
  2. Client scope: wizbrand-groups with Group Membership mapper (Full path: ON, added to ID & Access tokens).
  3. Attach scope to wizbrand-web as Default.
  4. Parse groups in Laravel with a tiny helper to get [orgId => role] with precedence.
  5. Enforce with middleware and (optionally) policies; cache the parsed map for speed.
Post Views: 1,517
  • Adding Group Membership
  • Adding Group Membership in Keycloak
  • Group Membership Claims in Keycloak
  • how to add keycloak group
  • Keycloak
  • keycloak authentication
  • keycloak Group
Subscribe
Login
Notify of
guest
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
  • Linux Server Diagnostic Commands: Complete Guide for Performance, Network & System Troubleshooting
  • The Ultimate Guide to CDOM – Certified DataOps Manager Certification
  • The Practical Path to AI Reliability: A Guide to the Certified MLOps Manager
  • Master the Machine Learning Lifecycle:Guide to Becoming a Certified MLOps Architect
  • How to Build a Project-Level AI Memory System That Works Across Codex, Claude, and Other AI Coding Tools
  • Certified MLOps Professional: A Deep Dive into the Certified MLOps Professional Certification
  • Certified MLOps Engineer : The Comprehensive Guide to Mastering Machine Learning Operations
  • Codex vs Claude: A Complete Practical Guide for Modern Developers (2026)
  • Certified AIOps Professional Program A Guide to Career Growth
  • Keycloak Multi-Client Architecture with Project-Based Email Validation (Student, Trainer, Company, Consulting)
  • Incorrect definition of table mysql.column_stats
  • Mautic and PHP 8.3 Compatibility Guide (2026)
  • Certified AIOps Engineer: The Complete Career Path and Certification Guide
  • How to Rename Apache Virtual Host Files Safely (Step-by-Step Guide for Linux)
  • AIOps Foundation Certification: Everything You Need to Know to Get Certified
  • DevOps to Certified Site Reliability Professional: A Senior Mentor’s Guide
  • Certified Site Reliability Manager Training, Preparation, and Career Mapping
  • Certified Site Reliability Architect: The Complete Career Guide
  • What Is a VPN? A Complete Beginner-to-Advanced Tutorial
  • How to Install, Secure, and Tune MySQL 8.4 on Ubuntu 24.04 for Apache Event MPM and PHP-FPM
  • Complete Guide to Certified Site Reliability Engineer Career
  • Certified DevSecOps Professional Step by Step
  • Certified DevSecOps Manager: Complete Career Guide
  • Certified DevSecOps Engineer: Skills, Career Path and Certification Guide
  • Step-by-Step: Become a Certified DevSecOps Architect
  • Tuning PHP 8.3 for Apache Event MPM and PHP-FPM on Ubuntu: A Complete Step-by-Step Production Guide
  • Complete Step-by-Step Guide to Configure Apache Event MPM, Create index.php, Set Up VirtualHost, and Fix Ubuntu Default Page
  • Convert XAMPP Apache to Event MPM + System PHP-FPM
  • The Gateway to System Observability Engineering (MOE)
  • How to Finetune Apache and Prove It Works: A Real-World Guide to Testing Performance, Concurrency, HTTP/2, Memory, CPU, and Security

Recent Comments

  1. emmy day on SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘provider’ in ‘field list’
  2. digital banking on Complete Tutorial: Setting Up Laravel Telescope Correctly (Windows + XAMPP + Custom Domain)
  3. SAHIL DHINGRA on How to Uninstall Xampp from your machine when it is not visible in Control panel programs & Feature ?
  4. Abhishek on MySQL: List of Comprehensive List of approach to secure MySQL servers.
  5. Kristina on Best practices to followed in .httacess to avoid DDOS attack?

Archives

  • April 2026
  • March 2026
  • February 2026
  • January 2026
  • December 2025
  • November 2025
  • October 2025
  • September 2025
  • August 2025
  • July 2025
  • June 2025
  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022

Categories

  • Ai
  • AI Blogging
  • AiOps
  • ajax
  • Android Studio
  • Antimalware
  • Antivirus
  • Apache
  • Api
  • API Security
  • Api Testing
  • APK
  • Aws
  • Bike Rental Services
  • ChatGPT
  • Code Linting
  • Composer
  • cPanel
  • Cyber Threat Intelligence
  • Cybersecurity
  • Data Loss Prevention
  • Database
  • dataops
  • Deception Technology
  • DeepSeek
  • Devops
  • DevSecOps
  • DevTools
  • Digital Asset Management
  • Digital Certificates
  • Docker
  • Drupal
  • emulator
  • Encryption Tools
  • Endpoint Security Tools
  • Error
  • facebook
  • Firewalls
  • Flutter
  • git
  • GITHUB
  • Google Antigravity
  • Google play console
  • Google reCAPTCHA
  • Gradle
  • Guest posting
  • health and fitness
  • IDE
  • Identity and Access Management
  • Incident Response
  • Instagram
  • Intrusion Detection and Prevention Systems
  • jobs
  • Joomla
  • Keycloak
  • Laravel
  • Law News
  • Lawyer Discussion
  • Legal Advice
  • Linkedin
  • Linkedin Api
  • Linux
  • Livewire
  • Mautic
  • Medical Tourism
  • MlOps
  • MobaXterm
  • Mobile Device Management
  • Multi-Factor Authentication
  • MySql
  • Network Traffic Analysis tools
  • Paytm
  • Penetration Testing
  • php
  • PHPMyAdmin
  • Pinterest Api
  • Quora
  • SAST
  • SecOps
  • Secure File Transfer Protocol
  • Security Analytics Tools
  • Security Auditing Tools
  • Security Information and Event Management
  • Seo
  • Server Management Tools
  • Single Sign-On
  • Site Reliability Engineering
  • soft 404
  • software
  • SuiteCRM
  • SysOps
  • Threat Model
  • Twitter
  • Twitter Api
  • ubuntu
  • Uncategorized
  • Virtual Host
  • Virtual Private Networks
  • VPNs
  • Vulnerability Assessment Tools
  • Web Application Firewalls
  • Windows Processor
  • Wordpress
  • WSL (Windows Subsystem for Linux)
  • X.com
  • Xampp
  • Youtube
©2026 DevSecOps Now!!! | WordPress Theme: EcoCoded
wpDiscuz