Authentication, social login, and team invites in Laravel SaaS: the complete guide

How to build a complete auth system for Laravel SaaS products, covering registration, social login with Google and GitHub, email verification, team invitations, and role-based workspace access.

R
Raşit Apalak
·
·
Updated
·
8 min read

Quick Answer

Quick answer: Authentication, social login, and team invites in Laravel SaaS: the complete guide

How to build a complete auth system for Laravel SaaS products, covering registration, social login with Google and GitHub, email verification, team invitations, and role-based workspace access.

See supporting documentation

Authentication, social login, and team invites in Laravel SaaS: the complete guide

Authentication in a SaaS application is not just login and logout. It is the entire chain from first visit to active team member: registration, email verification, social login, workspace creation, team invitations, and role-based access control.

Getting this right early prevents painful retrofits later. This guide walks through how to architect a complete auth system for a Laravel SaaS product.

Why SaaS auth is different from regular app auth

A traditional web app usually has one account model: user registers, user logs in, user uses the app.

SaaS products add layers:

ConcernTraditional appSaaS product
Account unitIndividual userUser belongs to one or more workspaces
OnboardingRegister and useRegister, create workspace, invite team
AuthorizationGlobal roles (admin/user)Workspace-scoped roles (owner/admin/member)
Login optionsEmail/passwordEmail/password + social providers
Billing contextPer userPer workspace, often per seat
Session contextSingle contextWorkspace-aware context switching

Building auth as if you are building a single-user app and then adding workspace context later is one of the most expensive refactors in SaaS development.

Part 1: Registration and email verification

Registration flow architecture

A clean SaaS registration flow does more than create a user record:

  1. Validate input (email uniqueness, password strength)
  2. Create user record
  3. Create default workspace for the user
  4. Attach user to workspace as owner
  5. Send verification email
  6. Redirect to onboarding (not directly to dashboard)

Why workspace creation happens at registration

If you wait for users to manually create their first workspace, you introduce an empty state that confuses onboarding. Creating a default workspace during registration means the user immediately has context.

// Inside your registration action/service
$user = User::create([
    'name' => $validated['name'],
    'email' => $validated['email'],
    'password' => Hash::make($validated['password']),
]);
 
$workspace = Workspace::create([
    'name' => $user->name . "'s Workspace",
    'owner_id' => $user->id,
]);
 
$workspace->members()->attach($user->id, ['role' => 'owner']);
 
$user->update(['current_workspace_id' => $workspace->id]);

Email verification

Laravel ships with email verification via the MustVerifyEmail interface. For SaaS, enforce verification before allowing access to billing or team features, not necessarily before the user can see the dashboard.

This gives users a quick first experience while still ensuring verified emails before critical actions.

Part 2: Social login with Google and GitHub

Why social login matters for SaaS

Social login reduces registration friction. For developer-facing SaaS products, GitHub login is especially effective because the audience already has accounts and trusts the provider.

Conversion impact:

  • Fewer abandoned registrations (no password creation step)
  • Higher trust signal from recognized OAuth providers
  • Reduced password reset support volume

Implementation with Laravel Socialite

Laravel Socialite handles the OAuth flow. The key architectural decision is how to reconcile social accounts with email/password accounts.

Account linking strategy

When a user signs in with Google or GitHub, you need to handle three cases:

ScenarioWhat to do
New email, no existing accountCreate user + default workspace
Email matches existing account, first social loginLink social identity to existing user
Email matches existing account, social already linkedLog in directly
public function handleProviderCallback(string $provider)
{
    $socialUser = Socialite::driver($provider)->user();
 
    $user = User::where('email', $socialUser->getEmail())->first();
 
    if (!$user) {
        $user = $this->createUserWithWorkspace($socialUser, $provider);
    }
 
    $this->linkSocialIdentity($user, $socialUser, $provider);
 
    Auth::login($user, remember: true);
 
    return redirect()->intended('/dashboard');
}

Storing social identities

Use a separate social_accounts table rather than putting provider columns on the users table. This supports multiple providers per user cleanly:

Schema::create('social_accounts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('provider');
    $table->string('provider_id');
    $table->string('provider_token')->nullable();
    $table->string('provider_refresh_token')->nullable();
    $table->timestamps();
 
    $table->unique(['provider', 'provider_id']);
});

Security considerations

  • Always verify the email from the OAuth provider is verified before trusting it.
  • Do not allow social login to bypass email verification for existing accounts.
  • Store tokens encrypted if you need them for API access later.

Part 3: Two-factor authentication

For SaaS products handling billing or sensitive business data, two-factor authentication (2FA) should be available even if not enforced by default.

Implementation approach

  • Use TOTP (Time-based One-Time Password) with apps like Google Authenticator or Authy.
  • Store the 2FA secret encrypted in the users table.
  • Generate recovery codes during setup so users are not locked out.
  • Check 2FA status during the login flow, after password validation but before session creation.

When to enforce 2FA

  • Optional for all users (recommended default).
  • Required for workspace owners and admins (good middle ground).
  • Required for all users (enterprise-tier feature, common in B2B SaaS).

Part 4: Team invitations

Team invitations are where SaaS auth diverges most from traditional applications.

Invitation flow architecture

  1. Workspace admin sends invite with email and role.
  2. System creates invitation record with unique token and expiration.
  3. Invite email sent with accept link.
  4. Recipient clicks link and either registers or logs in.
  5. System validates token, attaches user to workspace with specified role.
  6. Invitation marked as accepted.

Invitation data model

Schema::create('workspace_invitations', function (Blueprint $table) {
    $table->id();
    $table->foreignId('workspace_id')->constrained()->cascadeOnDelete();
    $table->foreignId('invited_by')->constrained('users')->cascadeOnDelete();
    $table->string('email');
    $table->string('role')->default('member');
    $table->string('token')->unique();
    $table->timestamp('expires_at');
    $table->timestamp('accepted_at')->nullable();
    $table->timestamps();
 
    $table->unique(['workspace_id', 'email']);
});

Handling the accept flow

The invitation accept logic needs to handle two paths:

Path A: Recipient already has an account

public function accept(string $token)
{
    $invitation = WorkspaceInvitation::where('token', $token)
        ->whereNull('accepted_at')
        ->where('expires_at', '>', now())
        ->firstOrFail();
 
    $user = Auth::user() ?? User::where('email', $invitation->email)->first();
 
    if (!$user) {
        return redirect()->route('register', ['invitation' => $token]);
    }
 
    $invitation->workspace->members()->attach($user->id, [
        'role' => $invitation->role,
    ]);
 
    $invitation->update(['accepted_at' => now()]);
 
    return redirect('/dashboard');
}

Path B: Recipient needs to register

After registration, check for pending invitations matching the new user's email and auto-accept them.

Rate limiting invitations

Prevent invitation abuse by limiting sends per workspace per hour. This protects against both spam and accidental bulk invites.

Part 5: Role-based access in workspaces

Role model design

Keep roles simple and workspace-scoped:

RoleCapabilities
OwnerFull control, billing, delete workspace
AdminManage members, manage resources
MemberUse workspace resources, no admin actions

Store the role on the workspace_user pivot table, not on the user record. A user can be an owner of one workspace and a member of another.

Authorization with Laravel policies

Use policies that receive the workspace context:

class ProjectPolicy
{
    public function update(User $user, Project $project): bool
    {
        $membership = $project->workspace->members()
            ->where('user_id', $user->id)
            ->first();
 
        if (!$membership) {
            return false;
        }
 
        return in_array($membership->pivot->role, ['owner', 'admin'])
            || $project->user_id === $user->id;
    }
}

Middleware for workspace context

Set the active workspace early in the request lifecycle so controllers and policies can reference it without repeated lookups:

class SetWorkspaceContext
{
    public function handle(Request $request, Closure $next)
    {
        $user = $request->user();
        $workspace = $user->currentWorkspace;
 
        if (!$workspace || !$workspace->members->contains($user)) {
            abort(403, 'No active workspace.');
        }
 
        app()->instance('currentWorkspace', $workspace);
 
        return $next($request);
    }
}

Part 6: Session management for multi-workspace users

Users who belong to multiple workspaces need context switching without logging out.

Workspace switching

Store the active workspace ID on the user record (current_workspace_id). Switching workspaces updates this value and redirects to the dashboard.

Session security

  • Invalidate sessions on password change.
  • Allow users to view and revoke active sessions from other devices.
  • Set reasonable session lifetimes (shorter for billing pages if needed).

How these pieces connect in a SaaS product

The full auth lifecycle for a SaaS user:

  1. User discovers product and registers (email/password or social).
  2. Default workspace is created automatically.
  3. User verifies email.
  4. User configures workspace and invites teammates.
  5. Teammates accept invitations and join with assigned roles.
  6. Workspace owner sets up billing (tied to workspace, not user).
  7. Members collaborate within workspace-scoped authorization.
  8. Users can create or join additional workspaces.

Every step in this chain needs to work reliably before you ship to customers.

Common auth mistakes in Laravel SaaS apps

  • No workspace creation at registration. Users land on an empty dashboard with no context.
  • Social login creates duplicate accounts. Missing email-based account linking causes separate accounts for the same person.
  • Roles stored on the user model. This breaks when users belong to multiple workspaces with different roles.
  • Invitation tokens that never expire. Stale invitations become a security risk.
  • No 2FA option. Enterprise buyers often require it before evaluating your product.

Start with the auth foundation already built

Implementing this full auth stack (registration, social login, verification, invitations, roles, workspace context) from scratch takes significant engineering time and testing effort.

If you want to skip the auth infrastructure work and start building product features, SaasForgeKit provides this entire auth system out of the box, including Google and GitHub social login, workspace-scoped roles, team invitations, and session management.

Share this post:

Ready to ship faster?

Build your SaaS with a production-ready foundation

Launch with authentication, billing, tenancy, and team workflows already in place, then focus on the features that make your product unique.

Related posts