Migrate from other authentication systems to BetterAuth.
| Feature | LexikJWT | BetterAuth |
|---|---|---|
| Token Format | JWT (RS256/HS256) | Paseto V4 |
| Refresh Tokens | Manual | Built-in |
| OAuth | Not included | Built-in |
| 2FA | Not included | Built-in |
| Session Support | No | Yes (hybrid mode) |
| Multi-tenant | No | Yes |
composer require betterauth/symfony-bundle
composer remove lexik/jwt-authentication-bundle
Before (LexikJWT):
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600
After (BetterAuth):
# config/packages/better_auth.yaml
better_auth:
mode: 'api'
secret: '%env(BETTER_AUTH_SECRET)%'
token:
lifetime: 3600
refresh_lifetime: 2592000
Before:
# config/packages/security.yaml
security:
firewalls:
api:
stateless: true
jwt: ~
After:
# config/packages/security.yaml
security:
providers:
better_auth:
id: BetterAuth\Symfony\Security\BetterAuthUserProvider
firewalls:
api:
stateless: true
provider: better_auth
custom_authenticators:
- BetterAuth\Symfony\Security\BetterAuthAuthenticator
Before (LexikJWT):
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
class AuthController
{
public function login(JWTTokenManagerInterface $JWTManager)
{
$token = $JWTManager->create($user);
return $this->json(['token' => $token]);
}
}
After (BetterAuth):
use BetterAuth\Core\AuthManager;
class AuthController
{
public function login(AuthManager $authManager, Request $request)
{
$result = $authManager->signIn(
$email,
$password,
$request->getClientIp(),
$request->headers->get('User-Agent')
);
return $this->json([
'access_token' => $result['access_token'],
'refresh_token' => $result['refresh_token'],
'user' => $result['user'],
]);
}
}
Before (LexikJWT):
// Store single token
localStorage.setItem('token', response.token);
// Use in header
headers: { 'Authorization': `Bearer ${token}` }
After (BetterAuth):
// Store both tokens
localStorage.setItem('access_token', response.access_token);
localStorage.setItem('refresh_token', response.refresh_token);
// Implement refresh logic
if (response.status === 401) {
const newTokens = await refreshToken();
// Retry request
}
Generate migration for new entities:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
composer require betterauth/symfony-bundle
php bin/console better-auth:install
Before:
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
private ?int $id = null;
private ?string $email = null;
private ?string $password = null;
private array $roles = [];
// Standard getters/setters
}
After:
use BetterAuth\Core\Entities\User as BetterAuthUser;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class User extends BetterAuthUser
{
// BetterAuth handles all base fields
// Add custom fields here
}
Before (Form Login):
// LoginFormAuthenticator.php
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
public function authenticate(Request $request): Passport
{
// Form-based authentication
}
}
After (API-based):
// BetterAuth handles authentication automatically
// Just use the /auth/login endpoint
composer remove friendsofsymfony/user-bundle
composer require betterauth/symfony-bundle
// Create migration command
class MigrateUsersCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$oldUsers = $this->oldUserRepository->findAll();
foreach ($oldUsers as $oldUser) {
// Password hashes are compatible (both use bcrypt/argon2)
$newUser = new User();
$newUser->setEmail($oldUser->getEmail());
$newUser->setPassword($oldUser->getPassword()); // Keep hash
$newUser->setName($oldUser->getUsername());
$this->entityManager->persist($newUser);
}
$this->entityManager->flush();
return Command::SUCCESS;
}
}
Before (FOSUser templates):
{% extends "[@FOSUser](https://github.com/FOSUser)/layout.html.twig" %}
After:
{# Create your own templates or use API #}
| Sanctum | BetterAuth |
|---|---|
| Personal Access Tokens | Paseto V4 Tokens |
$request->user() |
$this->authManager->getCurrentUser($token) |
auth()->user() |
Same, via Symfony Security |
// ExportUsersCommand.php
protected function execute(InputInterface $input, OutputInterface $output): int
{
$users = $this->connection->fetchAllAssociative('SELECT * FROM users');
file_put_contents('users.json', json_encode($users, JSON_PRETTY_PRINT));
return Command::SUCCESS;
}
// ImportUsersCommand.php
protected function execute(InputInterface $input, OutputInterface $output): int
{
$users = json_decode(file_get_contents('users.json'), true);
foreach ($users as $userData) {
$user = new User();
$user->setEmail($userData['email']);
$user->setPassword($userData['password']); // Hash is compatible
$user->setName($userData['name'] ?? '');
$user->setEmailVerified($userData['email_verified'] ?? false);
$this->entityManager->persist($user);
}
$this->entityManager->flush();
$output->writeln(sprintf('Imported %d users', count($users)));
return Command::SUCCESS;
}
# Truncate old token tables
php bin/console doctrine:query:sql "TRUNCATE TABLE old_tokens"
// Force all users to re-login
// Their old tokens will be invalid
// New BetterAuth tokens will be issued on login
# config/packages/security.yaml
security:
firewalls:
# New BetterAuth endpoints
api_v2:
pattern: ^/api/v2
custom_authenticators:
- BetterAuth\Symfony\Security\BetterAuthAuthenticator
# Old system (for migration period)
api_v1:
pattern: ^/api/v1
# Old authenticator
How can I help you explore Laravel packages today?