tecnocreaciones/ajax-fos-user-bundle
Tecnocreaciones AjaxFOSUserBundle is a Symfony bundle that adds AJAX support to FOSUserBundle, enabling asynchronous user registration, login, and profile actions without full page reloads. It provides routes, controllers, and templates for smoother user flows.
First, calibrate expectations: this is a Symfony 2.x/3.x bundle for FOSUserBundle, not a Laravel package. Its whole job is to make FOSUserBundle's form login return JSON instead of a 302 redirect when the request is an XMLHttpRequest. If you are working in a Laravel codebase, the equivalent is Fortify/Sanctum with a custom LoginResponse — there is nothing here to install. If you are on a legacy Symfony app, read on.
Minimal steps:
composer require tecnocreaciones/ajax-fos-user-bundle. The single tagged release is from 2017, so on anything newer than Symfony 3.4 you will likely need :dev-master and may hit PHP 7.1+ / Symfony\Component\Security\Core\Authentication\Token\TokenInterface signature issues.new Tecnocreaciones\AjaxFOSUserBundle\AjaxFOSUserBundle() in app/AppKernel.php.Resources/config/services.xml — the authoritative list of service IDs and tags. Everything you wire in security.yml must match these.Security/Authentication/Handler/ — the success and failure handlers that build the JSON payloads.DependencyInjection/ — what is actually configurable.POST /login_check with _username, _password, _csrf_token, and X-Requested-With: XMLHttpRequest, receive 200 {"success":true,...} or 401 {"success":false,"message":"..."} instead of a redirect.Firewall wiring. Do not replace form_login; decorate it. Point success_handler and failure_handler at the bundle's services inside your existing firewall:
firewalls:
main:
form_login:
login_path: /login
check_path: /login_check
success_handler: tecnocreaciones.ajax_fos_user.handler.success # verify ID in services.xml
failure_handler: tecnocreaciones.ajax_fos_user.handler.failure
anonymous: ~
remember_me: { secret: "%secret%" }
If the bundle ships a request listener that auto-detects AJAX, leave it tagged as-is and use the handlers only as a fallback for non-AJAX clients.
CSRF token bootstrap. The most common frontend pattern is a two-request flow: GET /login (or a tiny controller that renders the token) to scrape _csrf_token from the form, then POST /login_check with it. Cache the token in memory; do not fetch it on every keystroke.
Client wrapper. Centralize the call rather than scattering $.ajax:
async function login(username, password, csrf) {
const res = await fetch('/login_check', {
method: 'POST',
credentials: 'include', // session cookie must round-trip
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest', // this header is the whole trigger
},
body: new URLSearchParams({ _username: username, _password: password, _csrf_token: csrf }),
});
return { ok: res.ok, data: await res.json().catch(() => ({})) };
}
Add a "whoami" endpoint. FOSUserBundle predates SPAs and has no JSON identity endpoint. Write a five-line controller returning $this->getUser() serialized, guarded by @Security("is_granted('IS_AUTHENTICATED_FULLY')"). Your frontend needs it to rehydrate after a page refresh.
Registration and password reset. This bundle does not cover them. The repeatable pattern is a thin controller that calls $form->handleRequest($request) and, when $form->isValid() is false, returns JsonResponse(['errors' => ...]) built from $form->getErrors(true). Return 400, not 200, so clients can branch on status.
Functional tests. Send the AJAX header in your test client; otherwise you are testing the redirect path and will get green tests that do not exercise the code you care about:
$client->request('POST', '/login_check', [...], [], ['HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest']);
$this->assertJsonResponse($client->getResponse(), 200);
composer.json and commit composer.lock.fetch and most axios configs do not send X-Requested-With. Without it you silently get a 302 and HTML — which looks like "the bundle is broken" when it just was not consulted.credentials: 'include' and the server must return an explicit Access-Control-Allow-Origin (never *) plus Access-Control-Allow-Credentials: true. The success handler still rotates the session and may set a remember-me cookie; if Set-Cookie does not reach the browser, login "succeeds" and the next request is anonymous._csrf_token vs _csrf_login). Read the generated HTML once and copy the name exactly.getMessage(). BadCredentialsException vs UsernameNotFoundException is a user-enumeration vector — override the handler in prod to return a generic message.security.yml. This keeps your custom payload (extra user fields, redirect URL, flash messages) out of vendor/.security log channel at INFO; the firewall decision (AuthenticationException vs AccessDeniedException vs nothing at all) tells you instantly whether the listener ran. If you see the redirect handler in play, the header or the firewall check_path is wrong.json_login natively (security.yaml → firewalls.main.json_login.check_path). It does exactly what this bundle does, is maintained, and requires no third-party code. For any app you are not forced to keep on Symfony 2/3, plan to delete this bundle and switch to json_login; budget it as a half-day refactor, not a rewrite.How can I help you explore Laravel packages today?