Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Scim Server Laravel Package

arietimmerman/laravel-scim-server

Add a SCIM 2.0 server to Laravel apps for automated user and group provisioning. Exposes standard REST endpoints so identity providers like Okta, Azure AD, and OneLogin can securely sync users, groups, and attributes.

View on GitHub
Deep Wiki
Context7

Getting Started

  • composer require limosa-io/laravel-scim-server, then publish its config (php artisan vendor:publish and pick the SCIM provider tag). Nearly every decision you'll make lives in config/scim.php: route prefix, middleware stack, auth token/guard, and the resource map.
  • Read in this order: config/scim.php → the README section on resources → a generated resource stub. The stub is the real documentation; it pins down the exact attribute-builder API and method signatures for your version.
  • Mount the server behind your own auth: SCIM is machine-to-machine, so configure a bearer token (or a dedicated guard) per IdP tenant and put it in the middleware list. Do not let the package routes inherit web middleware — CSRF and session handling will break provisioning.
  • First use case: Okta / Entra ID (Azure AD) user provisioning into your users table. Map userName, name.givenName/familyName, emails, active, externalId in a single resource class, run the IdP's "test connection" + "provision user", and watch storage/logs/laravel.log for the raw payloads.
  • Smoke-test with curl -H "Authorization: Bearer $TOKEN" https://app.test/scim/v2/Users?filter=userName eq "[email protected]" before touching the IdP. Getting the filter syntax right by hand once saves hours of IdP-side debugging.

Implementation Patterns

  • One resource class per SCIM type, mapping only. Keep attribute mapping declarative (name → column/accessor) and push side effects — invitations, role assignment, Slack notifications — into model events or a service layer. The SCIM endpoint should look like a thin CRUD adapter over your domain.
  • Declare mutability/requiredness/uniqueness deliberately. These flags drive both validation and the schema discovery endpoints IdPs poll at setup time. Getting mutable, required, caseExact, and uniqueness wrong is the usual cause of "the IdP keeps retrying and creating duplicates."
  • Idempotency on create. IdPs retry aggressively. Use externalId for dedupe, and catch unique-constraint violations to return a proper SCIM 409 with scimType: uniqueness instead of a 500 — a 500 makes Okta retry and sometimes mark the integration broken.
  • PATCH is where integrations die. Support replace/add/remove with path expressions, including filtered paths (emails[type eq "work"].value) and the no-path "merge object" form. Write feature tests using payloads captured from the real IdP, not hand-authored ones.
  • Group membership direction. Decide explicitly whether SCIM is the source of truth for group membership or whether you mirror it into a local pivot table. Mixed ownership produces silent drift that only shows up when someone is offboarded but still in a role.
  • Scoping belongs in the base query. If you multi-tenant (or restrict to account_type = 'employee'), express that as a constraint on the resource's base Eloquent query rather than trying to bolt it onto the filter mapping. Then add your own regression tests for filter combinations — see the note on v1.5.3 below.
  • Index for filters and delta sync. user_name, external_id, and updated_at should be indexed; updated_at also powers meta.lastModified and the ?filter=meta.lastModified gt ... delta queries some IdPs use.
  • Error shape matters. Return SCIM-spec error bodies (schemas, detail, status, scimType) — IdP admin UIs surface detail verbatim to whoever is configuring the app, and it's your only channel to them.
  • Testing: feature tests against the real routes with a bearer token, plus a data-provider of filter strings (eq, co, sw, and, or, not, grouped parens, unsupported attributes) asserting both result sets and status codes.

Gotchas and Tips

  • v1.5.3 is a security patch — upgrade promptly. OR filter terms are now grouped so they can't escape the base query's constraints. Before this release, a filter like userName eq "a" or userName eq "b" could combine with your base query under SQL precedence and return rows outside your scope — in a multi-tenant setup, that's a cross-tenant read via the SCIM API. If you run anything below 1.5.3 with a narrowed base query, treat it as a leak and pin ^1.5.3. Add a regression test that hits a scoped resource with an or filter and asserts nothing outside the scope comes back.
  • 404 vs 200 after delete. IdPs treat 404 as "gone" and happily proceed; returning 200 with an empty body makes them think the user still exists and blocks re-creation of that username.
  • Changing your SCIM id strategy breaks matching. IdPs cache it. Prefer stable UUIDs on the resource over auto-increment primary keys, decided before you go to production.
  • Attribute names are case-insensitive in both payloads and filters per the RFC. Don't write switch statements that assume the IdP's exact casing — Azure and Okta differ.
  • Don't silently ignore unsupported filters. Return 400 with scimType: invalidFilter. Silently returning everything (or nothing) causes IdPs to conclude a user is absent and re-provision them, producing duplicates.
  • Array attributes round-trip badly if you flatten them. If you store one email column, still respond with a well-formed emails array including type and primary, or the IdP will rewrite it on every sync.
  • active: false is not a delete. Map it to your own deactivation (and decide what it means for tokens/sessions), and make sure deactivated users are still returned by GET — IdPs use that to confirm the deactivation landed.
  • Extension points: config-level resource registration, custom attribute types, and (depending on version) swappable controllers for individual verbs. If you need behaviour the package doesn't expose, check whether the route/controller is bindable in the container before reaching for a route override.
  • Version discipline: this package sits in the auth/provisioning path, so read every changelog and upgrade on patch releases rather than batching them into a quarterly bump.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo