Comprehensive authentication methods including OAuth, WebAuthn, 2FA, and magic links.
------
danwel implements a sophisticated multi-method authentication system that provides users with flexible, secure login options while maintaining strong security standards.
---
Users can have multiple authentication methods enabled simultaneously. The system prioritizes methods in this order:
1. **OAuth methods** (Google, GitHub, Apple)// Get user's available authentication methods
$methods = $user->getAuthenticationMethods();
// Returns: ['google', 'passkey', 'email'] etc.
// Get primary authentication method
$primaryMethod = $user->getAuthenticationMethod();
// Returns: 'google', 'github', 'passkey', 'email'
// Check specific methods
$user->hasOAuthMethods(); // true if any OAuth method
$user->hasPasskeys(); // true if WebAuthn configured
$user->hasIntentionalPassword(); // true if password explicitly set---
openid profile email | Name, email, avatar, Google ID |user:email | Name, email, avatar, GitHub ID |name email | Name, email, Apple ID |// config/services.php
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URI'),
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT_URI'),
],
'apple' => [
'client_id' => env('APPLE_CLIENT_ID'),
'client_secret' => env('APPLE_CLIENT_SECRET'),
'redirect' => env('APPLE_REDIRECT_URI'),
],// OAuth login routes
Route::get('/auth/{provider}', [AuthController::class, 'redirectToProvider']);
Route::get('/auth/{provider}/callback', [AuthController::class, 'handleProviderCallback']);
// Handle OAuth callback
public function handleProviderCallback($provider)
{
$providerUser = Socialite::driver($provider)->user();
// Find or create user
$user = User::updateOrCreate([
$provider . '_id' => $providerUser->getId(),
], [
'name' => $providerUser->getName(),
'email' => $providerUser->getEmail(),
// Avatar handling, etc.
]);
Auth::login($user);
return redirect()->intended('/dashboard');
}---
danwel uses the laragear/webauthn package for passkey support:
// User model implements WebAuthnAuthenticatable
class User extends Authenticatable implements WebAuthnAuthenticatable
{
use WebAuthnAuthentication;
// Check if user has passkeys
public function hasPasskeys(): bool
{
return $this->webAuthnCredentials()->exists();
}
}// WebAuthn routes (provided by package)
Route::middleware(['web', 'auth'])->group(function () {
Route::get('/webauthn/register/options', ...);
Route::post('/webauthn/register', ...);
Route::post('/webauthn/login/options', ...);
Route::post('/webauthn/login', ...);
});---
danwel supports multiple 2FA methods:
1. **TOTP Apps** - Google Authenticator, Authy, 1Password// Enable 2FA for user
$user->enableTwoFactorAuthentication();
// Generate QR code for TOTP app
$qrCode = $user->twoFactorQrCodeSvg();
// Verify TOTP code
$isValid = $user->confirmTwoFactorAuthentication($code);
// Generate recovery codes
$recoveryCodes = $user->recoveryCodes();Email-based 2FA provides a backup when TOTP is unavailable:
// Send email verification code
$code = random_int(100000, 999999);
session(['2fa_email_code' => $code, '2fa_expires' => now()->addMinutes(10)]);Mail::send(new TwoFactorCodeMail($user, $code));
// Ensure 2FA is completed
Route::middleware(['auth', 'verified', 'two-factor'])->group(function () {
// Protected routes
});---
Users can log in via email without passwords:
// Generate magic link
public function sendMagicLink(Request $request)
{
$user = User::where('email', $request->email)->first();
if ($user) {
$token = Str::random(60);
// Store token with expiration
Cache::put("magic_link_{$token}", $user->id, 900); // 15 minutes
// Send email with magic link
Mail::send(new MagicLinkMail($user, $token));
}
return response()->json(['message' => 'Magic link sent']);
}
// Authenticate via magic link
public function authenticateViaMagicLink($token)
{
$userId = Cache::pull("magic_link_{$token}");
if ($userId) {
$user = User::find($userId);
Auth::login($user);
return redirect()->intended('/dashboard');
}
return redirect()->route('login')->withErrors(['token' => 'Invalid or expired link']);
}---
For mobile apps or external integrations:
// Generate mobile login token
Route::post('/auth/mobile-token', [MobileAuthController::class, 'generateLoginToken'])
->middleware('throttle:5,1');
public function generateLoginToken(Request $request)
{
$user = auth()->user();
// Generate secure token
$token = Str::random(32);
// Store with short expiration (5 minutes)
Cache::put("mobile_token_{$token}", [
'user_id' => $user->id,
'created_at' => now(),
], 300);
return response()->json(['token' => $token]);
}
// Exchange token for API token
public function exchangeTokenForApiAccess(Request $request)
{
$tokenData = Cache::pull("mobile_token_{$request->token}");
if ($tokenData) {
$user = User::find($tokenData['user_id']);
$apiToken = $user->createToken('mobile_app')->plainTextToken;
return response()->json(['api_token' => $apiToken]);
}
return response()->json(['error' => 'Invalid token'], 401);
}---
Users can manage their active sessions and API tokens:
// List user's active tokens
GET /api/tokens
// Revoke specific token
DELETE /api/tokens/{tokenId}
// Revoke all tokens (except current)
DELETE /api/tokens
// Logout (revoke current token)
POST /api/logout// Token details
$tokenInfo = [
'id' => $token->id,
'name' => $token->name,
'abilities' => $token->abilities,
'last_used_at' => $token->last_used_at,
'created_at' => $token->created_at,
'ip_address' => $token->last_used_ip ?? 'Unknown',
'user_agent' => $this->parseUserAgent($token->user_agent),
];---
danwel can act as an OAuth provider for external applications:
// OAuth authorization endpoint
Route::get('/oauth/authorize', [ExternalAppAuthController::class, 'authorize']);
Route::post('/oauth/authorize', [ExternalAppAuthController::class, 'approve']);
// Authorization flow
public function authorize(Request $request)
{
// Validate client_id, redirect_uri, scope
$client = OAuthClient::where('client_id', $request->client_id)->first();
if (!$client || !$this->isValidRedirectUri($request->redirect_uri, $client)) {
abort(400, 'Invalid client or redirect URI');
}
// Show authorization prompt
return view('auth.external-app-authorize', compact('client', 'request'));
}External apps request specific scopes:
| Scope | Description | Access Level |read:user | Read user profile | Basic user info |read:time-blocks | Read time blocks | Time tracking data |write:time-blocks | Create/edit time blocks | Time tracking write |read:projects | Read projects/clients | Project data |---
// Authentication rate limits
'login' => 'throttle:5,1', // 5 attempts per minute
'magic-link' => 'throttle:3,1', // 3 magic links per minute
'mobile-token' => 'throttle:5,1', // 5 mobile tokens per minute
'api' => 'throttle:api', // API rate limiting// Security event notifications
class SecurityEventListener
{
public function handle($event)
{
switch ($event->type) {
case 'password_changed':
Mail::send(new PasswordChangedEmail($event->user));
break;
case '2fa_enabled':
Mail::send(new TwoFactorEnabledEmail($event->user));
break;
case '2fa_disabled':
Mail::send(new TwoFactorDisabledEmail($event->user));
break;
}
}
}---
javascript
// Frontend OAuth initiation
function initiateOAuth(provider) {
window.location.href = /auth/${provider};
}
// Handle OAuth return
// User is automatically logged in after successful OAuth callback
Adding Passkey
javascript
// Register new passkey
async function registerPasskey() {
try {
// Get registration options from server
const optionsResponse = await fetch('/webauthn/register/options');
const options = await optionsResponse.json();
// Create credential
const credential = await navigator.credentials.create({
publicKey: options.publicKey
});
// Send credential to server
await fetch('/webauthn/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
credential: {
id: credential.id,
rawId: Array.from(new Uint8Array(credential.rawId)),
response: {
clientDataJSON: Array.from(new Uint8Array(credential.response.clientDataJSON)),
attestationObject: Array.from(new Uint8Array(credential.response.attestationObject))
},
type: credential.type
}
})
});
alert('Passkey registered successfully!');
} catch (error) {
console.error('Passkey registration failed:', error);
}
}
``// Enable TOTP 2FA
Route::post('/user/two-factor-authentication', function (Request $request) {
$request->user()->enableTwoFactorAuthentication();
return back()->with('status', 'two-factor-authentication-enabled');
});
// Confirm 2FA setup
Route::post('/user/confirmed-two-factor-authentication', function (Request $request) {
$confirmed = $request->user()->confirmTwoFactorAuthentication($request->code);
if ($confirmed) {
event(new TwoFactorAuthenticationEnabled($request->user()));
return back()->with('status', 'two-factor-authentication-confirmed');
}
return back()->withErrors(['code' => 'Invalid authentication code']);
});---
This advanced authentication system provides users with flexible, secure login options while maintaining strong security standards across all authentication methods.