Skip to main content
This tutorial helps you implement a secure OAuth authentication system connecting a Next.js 15 frontend with a Laravel backend. You’ll create an Authorization Code Flow with PKCE (Proof Key for Code Exchange), providing robust security for your web applications.
Throughout this tutorial, CLI prompts and outputs may be shortened for clarity. When following along, you can select any option for the prompts shown that are not explicit in the tutorial - your choices won’t affect the final outcome.As some code blocks have also been shortened, please always refer to the complete source files available in the public GitHub repository.

Pre-requisites

Before starting, make sure you have these tools installed:
  • Git
  • PHP 8.2+
  • Composer
  • Node.js 20+

Initialize a new local project

Start by creating a new project directory and setting up Git:

Add test hostnames

Configure test domains for local development by adding entries to your hosts file:
Add the following entries to map test domains to your local machine:
These entries allow your browser to resolve the test domains to your local machine, creating a necessary multi-domain setup for OAuth testing.

Set up a new Next.js 15 project

Create your Next.js frontend application with TypeScript, Tailwind CSS, and the App Router:
Go into that newly created folder:
You can enhance your UI with shadcn/ui, a collection of reusable components:
Start the development server to test your setup:
You can leave the npm process running and create a new terminal session for the next steps.
If you use Cursor, here’s a .cursorrules file for Next.js.

Bootstrap a new Laravel project

Create a new Laravel backend that will serve as your OAuth server at the root of the project:
Now cd into the api folder to install additional packages.

Install Sail for local development

Laravel Sail provides a Docker-based development environment, making it easy to run your application with isolated dependencies:
Configure your Laravel environment by editing the .env file:
Create an alias for the sail command to simplify usage. You can add this line in your shell profile (.bashrc, .zshrc, etc.). Don’t forget to source that file afterwards or open a new terminal.
Start the Laravel environment:
Leave the sail process running. Open up a new terminal for any other command.
You should now see the Laravel welcome page at http://api.oauth-project.test/. For any troubleshooting needs, check the Sail documentation.

Install Telescope to debug requests

Laravel Telescope provides debugging tools for your application, including insights into requests, queries, and more:
Please refer to the Telescope documentation to configure it properly for production usage if needed.

Install Passport and the API package

Install Laravel Passport to handle OAuth authentication:
Add the HasApiTokens trait to your User model:
Configure the authentication guard in auth.php:

Install Fortify for auth boilerplate

Laravel Fortify provides authentication scaffolding without a specific frontend implementation:
If you use Cursor, here’s a .cursorrules file for Laravel..

Deploy both apps on Upsun

Commit your project before deploying to Upsun:

Upsun multi-apps configuration

Create a new Upsun project to host both applications:
Create a .upsun/config.yaml file to configure your multi-applications project. You can copy/paste the configuration file in the repository. This configuration:
  • Routes requests to the appropriate application based on the hostname (@ (root) and api.) L80-82. As we are using the {all} value, all added hostnames and Upsun auto-generated hostnames will be directed to the next application while all api. prefixed hostnames will be routed to the Laravel api.
  • Sets up PostgreSQL and Valkey (Redis-compatible) services L85-89
  • Configures both applications with proper build and deploy hooks
Now create a .environment file for Laravel to set environment variables:
Commit the configuration and the new .environment:

First deployment

Create an encryption key for Laravel as an environment variable. You can use your local key that has been generated in your local .env or create a new one with sail php artisan key:generate.
Deploy your project to Upsun:

Review of the OAuth flow

Let’s understand the OAuth flow you’ll implement.

Understanding OAuth grant types

OAuth 2.0 offers several grant types, each suited for different scenarios. For browser-based applications like yours, the Authorization Code Flow with Proof Key for Code Exchange (PKCE) provides the best security. PKCE (pronounced “pixy”) adds security by ensuring that only the application that initiated the authentication flow can exchange the authorization code for tokens. This prevents authorization code interception attacks. Auth0 provides a great introduction to different OAuth grant types. Here’s a helpful diagram by Alex Bilbie to choose the right grant: Grants

Your OAuth flow

Here’s the detailed flow between your Next.js client and Laravel backend: Diagram

Step-by-step breakdown

1. User starts the flow
  • User visits /next/dashboard in the browser.
  • Next.js server checks for an access_token:
    • ✅ If found: renders the dashboard.
    • ❌ If not found: redirects to /oauth/redirect.
2. OAuth redirect setup (Next.js)
  • Generates an OAuth authorize request with:
    • client_id
    • redirect_uri
    • state (for CSRF protection)
    • code_challenge (for PKCE)
  • Sends redirect to Laravel API /api/oauth/authorize.
3. Laravel API authorization flow
  • Laravel checks if the user is logged in:
    • ✅ If logged in: shows authorization form.
    • ❌ If not: displays login form.
  • After login:
    • Valid credentials → continues the flow.
    • Invalid credentials → shows error.
  • If user approves the app:
    • Laravel handles the authorization.
    • Creates and stores a client access_token in the database.
    • Redirects to /next/oauth/callback with the authorization code.
4. Callback handling (Next.js)
  • Validates the state to prevent CSRF attacks.
  • Exchanges the authorization code and code_verifier for an access_token.
  • Stores the token as an HttpOnly cookie.
  • Cleans up temporary cookies (state, challenge).
  • Redirects to /dashboard if successful.
  • Shows an error page or redirects to login if something fails.
This flow ensures:
  • Users authenticate directly with the authentication server
  • Access tokens remain secure and inaccessible to browser JavaScript
  • The application securely accesses protected resources

Laravel OAuth implementation with Passport

Laravel Passport simplifies OAuth implementation by handling token generation, storage, and validation. You need to create OAuth clients and implement login and authorization views.

Create a client and a test user

Create a test user and OAuth client through a database seeder:
This creates:
  • A test user with email test@example.com L19 with a default password set to password.
  • A public OAuth client (no secret, as required for PKCE) L29
  • A redirect URI matching your Next.js callback route L28
Run the seeder to populate your database:

OAuth and Fortify routes

Define API routes for user information and logout:
These routes are protected by the auth:api middleware, ensuring they’re only accessible with a valid access token. Add the login view to the boot method of FortifyServiceProvider L46:
Create the login view at api/resources/views/auth/login.blade.php. You can find an example in the GitHub repository. Customize the OAuth authorization form by publishing and modifying the Passport views:
Edit the authorization form at api/resources/views/vendor/passport/authorize.blade.php. Find an example in the GitHub repository. Test your login page at http://api.oauth-project.test/login to ensure it works correctly.

Next.js client implementation

Now for the client-side of your OAuth flow in Next.js. You need to create three key components:
  1. A dashboard component that checks authentication and loads user data
  2. The /oauth/redirect endpoint that initiates the authorization flow
  3. The /oauth/callback endpoint that processes the authorization response

Home and dashboard

Create a simple home page with a button linking to your dashboard. First, install the Button component:
Create a home page (next/src/app/page.tsx) with a dashboard link:
This creates a simple, centered button linking to your dashboard: Home Next, create a dashboard page that handles authentication checks. Don’t forget to also create the components it relies on. First, add the shadcn dependencies:
Create the dashboard page - View the full implementation.
This code:
  1. Checks for an access token in cookies
  2. Redirects to the OAuth flow if no token exists
  3. Fetches user data to verify the token is valid
  4. Redirects to the OAuth flow if the token is invalid or expired
  5. Renders the dashboard for authenticated users
Create the two other components that the dashboard includes:

The /oauth/redirect endpoint

Create a server action that initiates the OAuth flow at next/src/app/oauth/redirect/route.ts. View the full implementation.
This endpoint:
  1. Generates a random state value for CSRF protection
  2. Creates a code verifier and code challenge for PKCE
  3. Stores these values in HTTP-only cookies
  4. Builds the authorization URL with required parameters
  5. Redirects the user to the Laravel authorization endpoint
The prompt parameter controls the authorization server’s behavior:
  • login: Always show the login form
  • consent: Always show the authorization form
  • none: Skip forms if the user is already logged in
  • Empty: Use default behavior (may skip forms if logged in)
For local development, create a .env.local file with the necessary variables:
These values must match the OAuth client you created in the Laravel seeder.

The /oauth/callback endpoint

Create a callback endpoint to handle the authorization response at next/src/app/oauth/callback/route.ts. View the full implementation.
The callback endpoint:
  1. Retrieves and deletes the stored state and code verifier from cookies
  2. Validates the state parameter to prevent CSRF attacks
  3. Exchanges the authorization code for an access token using the code verifier
  4. Stores the access token in an HTTP-only cookie
  5. Redirects to the dashboard upon successful authentication
The HTTP-only cookie approach is crucial for security. Unlike localStorage, HTTP-only cookies can’t be accessed by JavaScript, protecting tokens from cross-site scripting (XSS) attacks. Add these additional environment variables to .env.local:

Local testing

Verify that all routes are available before testing the complete flow. Check Next.js routes:
Then check Laravel routes:
Now test the full OAuth flow. Go to http://next.oauth-project.test:3000/ and click “Go to the Dashboard.” This should trigger:
  1. Redirection to the Laravel login page (test@example.com / password)
  2. After login, presentation of the authorization form
  3. After approval, redirection back to your Next.js dashboard
Here’s what the flow looks like: OAuth demo flow
If you encounter issues and need to test again, clear cookies on both next.oauth-project.test:3000 and api.oauth-project.test domains. Existing sessions might interfere with testing.

Upsun environments & deployment

When deploying to Upsun, ensure your Next.js app can communicate with the Laravel OAuth server. Upsun provides environment variables with routes for all deployed apps. Get the API URL with this command:
Create a .environment file in the next directory to set environment variables during deployment:
This script extracts URLs for your applications from Upsun’s environment variables and sets OAuth endpoints accordingly. You still need to set the OAUTH_CLIENT_ID variable:
Before deploying, let’s move the faker dependency to the require section in composer.json instead of require-dev:
This allows your deployed environment to run the database seeder. Commit everything and deploy:
After deployment, seed the database and generate Passport keys:

Final test and review

Get your application URL with upsun url and test the complete flow. You’ll see:
  1. The Next.js homepage:
Flow Home
  1. Click “Go to dashboard” to trigger the OAuth flow and see the login form:
Flow Login
  1. Enter your credentials (default password: password) to see the authorization form:
Flow Authorization
  1. Click “Authorize” to complete the flow and access your dashboard:
Flow Dashboard

Implementing the logout feature

A complete authentication system needs a secure logout process. This requires:
  1. Clearing tokens stored as HTTP-only cookies in Next.js
  2. Revoking the token on the Laravel side
  3. Clearing the session on the Laravel side
Add a logout endpoint to your Laravel API routes. Note: You might already have that change if you copy pasted the file from the example repository
This endpoint revokes the access token and deletes the user’s session. Create a /logout endpoint in Next.js. View the full implementation.
This endpoint calls the Laravel logout API, deletes authentication cookies, and redirects to the homepage. To actually execute the logout, a handler is present in your user-dropdown component. View the full implementation.

Conclusion

You’ve built a secure OAuth authentication system between Next.js and Laravel using the Authorization Code Flow with PKCE. This approach provides strong security by:
  • Keeping tokens in HTTP-only cookies, protected from JavaScript access
  • Using state parameters to prevent CSRF attacks
  • Adding PKCE protection against authorization code interception
  • Properly revoking tokens during logout
You can extend this system with additional features like:
  • Refresh token handling for longer sessions
  • Scoped permissions for granular access control
  • Role-based authorization
  • Multi-factor authentication
For more information, check these resources: Find the complete code for this tutorial in the GitHub repository.
Last modified on June 9, 2026