The Northwestern Laravel Starter provides a flexible, multi-method authentication system designed to handle both Northwestern users (via NetID/SSO) and external collaborators (via passwordless verification codes or Access Tokens).
NetID Single Sign-On
Northwestern SSO (Entra ID or Online Passport)
Primary authentication method for Northwestern users via NetID.
Entra ID (OAuth2) or Online Passport (cookie-based)
Multi-factor authentication support
Automatic user provisioning
Session-based authentication
Passwordless Verification Codes
Email OTP Authentication
Email-based passwordless authentication for external users.
No password management
Time-limited verification codes
Rate limiting protection
Invite-only access
Access Tokens
Bearer Token Authentication
Long-lived tokens for programmatic API access.
Multiple tokens per user
IP allowlisting
Token expiration
Northwestern users authenticate via single sign-on using their NetID credentials. The starter supports two SSO providers — Microsoft Entra ID (OAuth2) and Online Passport (agentless WebSSO via ForgeRock) — and auto-detects which one to use based on your configured credentials. See the WebSSO / Entra ID documentation for provider-specific details.
User visits protected route
User attempts to access a route requiring authentication
Redirect to SSO
Application redirects to Northwestern’s authentication service
User authenticates
User enters NetID and password (+ MFA if enabled)
Callback with token
SSO provider redirects back with authentication token
User provisioning
Application creates/updates user record from Directory Search data
Session established
User is logged in and session cookie is set
When a Northwestern user logs in for the first time, the application:
Validates NetID from SSO response
Queries Directory Search API for user demographics
Creates user record with demographic data:
Full name
Email address
Department
Affiliations (student, faculty, staff, etc.)
etc.
Subsequent logins update the user’s demographic data to keep it synchronized.
External users (non-Northwestern) can access the application via time-limited verification codes sent to their email.
Admin creates local user
Administrator creates a local user account in the Filament panel
Verification code sent
Admin triggers a code email or user requests one themselves
User receives email
Email contains a time-limited verification code (valid for 10 minutes by default)
User enters code
Code authenticates the user and establishes a session
Code expires
Code becomes invalid after use or expiration
# Enable/disable local authentication
# Rate limit for login code requests per email (per hour)
LOCAL_AUTH_RATE_LIMIT_PER_HOUR = 10
# Rate limit for login code requests per IP (per hour)
LOCAL_AUTH_RATE_LIMIT_PER_IP_PER_HOUR = 20
# Where to redirect after successful login
LOCAL_AUTH_REDIRECT_AFTER_LOGIN = /
# Use a fixed, predictable verification code instead of random codes
LOCAL_AUTH_USE_FIXED_CODE = false
Tip
Setting LOCAL_AUTH_USE_FIXED_CODE=true in your local .env replaces random verification codes with a fixed, predictable code (e.g. 123456 for a 6-digit code). This avoids needing to check your mail trap every time you log in during development. This setting is blocked from running in production, develop, and QA environments as a safety measure.
Navigate to Users
Click Actions → Create Local User
Enter user details (name, email, etc.)
Create the user
use App\Domains\User\Models\ User ;
use App\Domains\User\Enums\ Affiliation ;
use App\Domains\Auth\Enums\ AuthType ;
use App\Domains\Auth\Actions\Local\ IssueLoginChallenge ;
' auth_type ' => AuthType :: Local ,
' primary_affiliation ' => Affiliation :: Affiliate ,
' email ' => ' jane.doe@example.com ' ,
// Send verification code
resolve ( IssueLoginChallenge :: class )( $user -> email , request () -> ip (), request () -> userAgent ());
Time-Limited Codes
Codes automatically expire after configured duration (default: 10 minutes)
Single-Use Codes
Each code can only be used once, preventing replay attacks
Rate Limiting
Limits login code requests to prevent abuse and enumeration attacks
Timing Attack Protection
Consistent response times prevent user enumeration via timing analysis
API users authenticate using long-lived Bearer tokens for programmatic access.
Admin creates API user
Administrator creates an API user and generates first token
Token delivered once
Plain-text token shown only at creation (never retrievable again)
Client stores token
Client securely stores token for API requests
Token in requests
Client includes token in Authorization: Bearer header
Middleware validates
AuthenticatesAccessTokens middleware validates token and IP allowlist
Request logged
API request logged with metrics and analytics
See the API Documentation for complete Access Token management details.
Every login creates a UserLoginRecord with a segment — a classification of the user at that moment. Segments are used by the login analytics dashboard (charts, stats, filters) and are captured at login time so historical metrics remain accurate even if user roles change later.
Segment Classification Color Super Admin User has the ManageAll permission Danger (red) External User User authenticates via local/passwordless auth Warning (amber) Other Everyone else (default) Gray
All charts, filters, exports, and stats dynamically pull from the UserSegment. Adding a new case automatically appears everywhere.
Add a case to the enum
case SuperAdmin = ' super-admin ' ;
case ExternalUser = ' external-user ' ;
case Faculty = ' faculty ' ; // New segment
Add a color and icon for the new case in getColor() and getIcon(). The label is auto-generated from the value (e.g., 'faculty' → "Faculty").
Add classification logic
In DetermineUserSegment, add a check above the default case:
$this-> isSuperAdmin ( $user ) => UserSegment :: SuperAdmin ,
$this-> isExternalUser ( $user ) => UserSegment :: ExternalUser ,
$this-> isFaculty ( $user ) => UserSegment :: Faculty ,
default => UserSegment :: Other ,
Cases are evaluated top-to-bottom — order matters when a user could match multiple segments.
Authentication endpoints use layered rate limiters to prevent brute force attacks and abuse. Limits are applied per IP and per identifier (email or challenge ID) to balance security with usability.
Variable Default Description RATE_LIMIT_AUTH_LOGIN_CODE_REQUEST_PER_MINUTE5Code request attempts per minute (per IP) RATE_LIMIT_AUTH_LOGIN_CODE_REQUEST_PER_EMAIL_PER_MINUTE3Code request attempts per minute (per email) RATE_LIMIT_AUTH_LOGIN_CODE_VERIFY_PER_MINUTE10Code verification attempts per minute (per IP) RATE_LIMIT_AUTH_LOGIN_CODE_VERIFY_PER_CHALLENGE_PER_MINUTE5Code verification attempts per minute (per challenge)
Variable Default Description RATE_LIMIT_AUTH_IMPERSONATE_PER_MINUTE10Impersonation actions per minute
All rate limiters are defined in config/rate-limiting.php.
For information on roles, permissions, and policies after a user is authenticated, see the Authorization documentation.