Adaptive Form submission fails authorization after SSO migration
An enterprise migrates AEM Forms from local LDAP login to SAML SSO via an IdP. Adaptive Forms with a submit action to a custom servlet (/bin/acme/forms/submit) start returning HTTP 403 for most users after migration. The servlet is registered with @SlingServlet(paths="/bin/acme/forms/submit", methods="POST") and checks request.getUserPrincipal(). Pre-migration, submissions succeeded. Post-migration, a few admin users succeed; regular users get 403. The AF uses guideSubmitAction with a csrf token. The Dispatcher allows POST to /bin/*. SAML assertion maps users to the forms-users group. The servlet's service user has jcr:read on /content/forms but no ACL on the data path /content/acme/customers.
1. Explain the 403 and why admins succeed, then give the fix.
Read the worked answer
The 403 is an authorization failure, not authentication: SSO authenticates the user, but the custom servlet performs work as a service user that lacks ACLs on /content/acme/customers. Admins succeed because they have broad read permissions that the service user does not.
Additional factors to check:
- The servlet checks
request.getUserPrincipal(); under SAML the principal exists but the servlet may then read data as the request user or as a service user. If it reads as the request user,forms-usersmust have read on the data path; if as a service user, that user needs the ACL. - CSRF: the AF must send a valid CSRF token; a missing/invalid token yields 403 too. Verify the token is fetched after SSO and included in the POST.
Fix: grant the appropriate principal (service user or forms-users) least-privilege jcr:read on /content/acme/customers via a rep:policy, and ensure the CSRF token flow works post-SSO. Do not broaden admin-like access.
2. How would you distinguish CSRF 403 from ACL 403 in production, and what hardening would you add?
Read the worked answer
Distinguish by response and logs: AEM's CSRF filter returns 403 with a CSRF-specific message/log entry (e.g., 'CSRF token' / 'Invalid CSRF token') before the servlet runs; ACL denials log an AccessDeniedException from the repository and the servlet's own logging executes. Add request-id logging at the servlet entry: if the log line appears, the 403 came from ACL inside the servlet; if not, it was the CSRF filter.
Hardening:
- Enforce CSRF on the custom servlet (AEM CSRF filter covers POST; ensure the AF includes the token).
- Use a dedicated service user with least privilege and map it with
ServiceUserMapped; avoid running as admin. - Validate and sanitize submitted data; restrict the servlet path in Dispatcher to the exact path and method.
- Add audit logging of principal, path, and outcome; alert on 403 spikes.
- Test the SSO + CSRF + ACL matrix in staging with a non-admin
forms-usersaccount.