jrojat/ccdn-user-security-bundle
Symfony bundle that adds security and account management to CCDNUser, including authentication, login/logout, registration, password reset, and account workflows. Integrates with Symfony Security and Doctrine to help manage secure user accounts and access in Symfony applications.
Before anything else: this is a Symfony 2.x bundle, not a Laravel package. It is a fork of the CCDN (Code Conspiracy Development Network) User Security Bundle. There is no ServiceProvider, no facade, no extra.laravel.providers, no Artisan command, no publishable config. Laravel's package discovery will never see it, and Composer will fight you on symfony/* and doctrine/* constraints if you try to install it into a modern Laravel app.
So there are exactly two starting points, and which one you take determines everything that follows.
Path A — you are on a legacy Symfony 2 app. Then this bundle is installable and the day-one steps are:
Install it with its siblings — it is not standalone:
composer require jrojat/ccdn-user-security-bundle
composer require ccdn/user-bundle ccdn/component-bundle
CCDNUserSecurityBundle owns the flows (registration, login, logout, password reset, account confirmation, email change). CCDNUserBundle owns the User / Group / Role entities and Doctrine mappings those flows read and write.
Register in AppKernel.php:
new CCDN\Bundle\UserBundle\CCDNUserBundle(),
new CCDN\Bundle\UserSecurityBundle\CCDNUserSecurityBundle(),
Import routes:
# app/config/routing.yml
ccdn_user_security:
resource: "@CCDNUserSecurityBundle/Resources/config/routing.yml"
prefix: /
Configure in config.yml (registration toggles, confirmation requirement, token TTLs).
Wire your own security.yml firewall — the bundle gives you controllers and routes, not a firewall. You still declare form_login, logout, and remember_me pointing at the bundle's paths.
Run the Doctrine schema tool for the user/role tables.
Path B — you are on Laravel (the overwhelmingly likely case). Do not install it. Use it as a feature checklist and implement natively. The first use case to look at is registration → email confirmation → login, because that is the one flow that touches every primitive you need:
MustVerifyEmail on the User model for the activation stepregister / login / logout / password.resetPassword::sendResetLink() and the shipped password_reset_tokens table for recoveryWhere to look first, either way: Resources/config/routing.yml (the actual URL surface), DependencyInjection/Configuration.php (the real config tree — this is the authoritative list of what is configurable, not the README), and the controller actions, which are thin and read as a flow spec. In a Laravel rewrite, read those three things and stop.
Legacy Symfony patterns (if you are actually on Symfony 2). The bundle is designed to be extended, not just consumed:
vendor/. Create app/Resources/CCDNUserSecurityBundle/views/... — the Symfony template loader resolves app-level paths before bundle paths. Same trick for translations.config.yml. The controllers and forms are DI services. Swap a form type or a mailer by aliasing the service ID rather than forking the class.security.yml is still yours. A very common integration failure is expecting the bundle to provide a working firewall. It does not. Copy the canonical form_login block and align check_path / login_path with the bundle's routes.CCDNUserBundle's User entity and tell Doctrine about the subclass, or you will fight mapping conflicts when you add your own columns.Laravel patterns — this is what "day-to-day leverage" actually looks like. Treat the bundle as a written spec and map each capability to a native primitive:
| CCDN capability | Laravel equivalent |
|---|---|
| Activation email + token | MustVerifyEmail, signed URLs, VerifyEmail notification |
| Resend activation | Rate-limited resend route (RateLimiter::for('verification')) |
| Password reset token | Password::sendResetLink, password_reset_tokens table |
| Change password (authenticated) | Hash::check + current_password validation rule |
| Email change with confirmation | Pending-email column + signed temporary URL |
| Login attempt tracking / lockout | RateLimiter, Login / Failed events |
| IP / last-login capture | EventServiceProvider listener on Login |
| Role / group management | spatie/laravel-permission or plain Gate/Policy |
The workflow I'd actually run, if a stakeholder insists on "using" this package:
If you are genuinely on Symfony 2 and must ship fast: vendor it. composer require into vendor/, then commit vendor/ or set up a private Satis/Path repository so your build is reproducible after the maintainer goes dark (which, at zero stars and zero dependents, assume has already happened). Fork-and-own is the only viable long-term posture.
Composer and autoload.
symfony/* ^6|^7 and PHP 8.1+. This bundle's constraints are ~2.x / PHP 5.x-era. Resolution fails outright; if it somehow resolves, you get two incompatible Symfony component trees in one autoloader, which produces bizarre "class not found" and method-signature errors that look like your code's fault.AppKernel registration in Symfony, which has no Laravel analogue at all.Framework glue is ~80% of the code. Symfony Forms, Swiftmailer, the Symfony Translator, Security's authentication-provider/voter model, Doctrine unit-of-work. Only the state-machine logic — token issuance, expiry, single-use semantics, resend cooldowns — is conceptually portable, and it is not isolated in a framework-agnostic layer. Budget accordingly: this is a reading exercise, not a port.
Security pitfalls the bundle does not solve for you (these apply whether you adopt it or reimplement):
Schedule::command('auth:clear-resets') or your own).Mail::queue(). In Symfony 2 with Swiftmailer, use the spool.Schema drift. If you ever run the Doctrine migrations alongside Laravel migrations in the same database, you will get two sources of truth for the user table. Don't. Pick one, and if migrating data out, do it as a one-shot export script.
Firewall / session gotchas (Symfony 2). The bundle expects the session to be established by your firewall. If check_path doesn't match a route the bundle actually registered, you get a silent redirect loop back to /login with no error — check php app/console debug:router first, then debug:container to confirm the services are wired. For remember-me and logout, the logout: target must be a GET-safe route or you'll see 405s from the bundle's controller.
Templates are Twig-namespaced. @CCDNUserSecurityBundle/.... Overrides go in app/Resources/, not next to the originals, or they'll be silently ignored.
Config quirks. DependencyInjection/Configuration.php is the only trustworthy reference for the config tree — the README drifts. Nested keys under registration.confirmation are where most of the interesting behavior (required vs. optional, resend window) lives.
Debugging, in order: debug:router → debug:container → the routing YAML → the controller. If routes are missing, you forgot the routing.yml import. If services are missing, the bundle isn't in AppKernel. If you get an auth loop, it's security.yml — always.
The honest bottom line for a Laravel dev. You cannot leverage this package day-to-day, because you cannot run it. Its value is as a checklist. Read routing.yml and Configuration.php, translate the flows to Fortify/Breeze primitives, and move on. The maintenance cost of adopting it — PHP 8 compatibility, Symfony 6/7 alignment, Doctrine→Eloquent, a test suite — is multiple weeks to reach parity with what composer require laravel/fortify gives you in an afternoon, with an active security process attached.
How can I help you explore Laravel packages today?