Laravel JWT Authentication with RSA (RS256) – Step-by-Step Tutorial

Laravel JWT Authentication with RSA (RS256) – Step-by-Step Tutorial

This tutorial shows how to build a secure, enterprise-grade authentication system in Laravel using:

  • JWT Authentication (RS256)

  • RSA public/private key encryption

  • Encrypted login passwords

  • Protected API routes

  • Production-ready best practices

What This Tutorial Covers

  • JWT Authentication using RS256 (Public / Private Key)

  • RSA password encryption (Frontend → Backend)

  • Secure Register, Login, Logout APIs

  • Protected API routes

  • Laravel API best practices

Requirements

  • PHP 8.1+

  • Laravel 10 / 11 / 12

  • Composer

  • OpenSSL enabled (PHP & CLI)

  • Postman (for API testing)

  • Basic Laravel knowledge

1️⃣ Create a New Laravel Project

composer create-project laravel/laravel jwt-rsa-auth

2️⃣ Install Laravel API Scaffolding

Prepare Laravel for API authentication:

php artisan install:api

This command enables API middleware and token-based authentication suitable for modern Laravel APIs.

3️⃣ Configure Database

Edit your .env file:

DB_DATABASE=jwt_rsa_api DB_USERNAME=root DB_PASSWORD=

Run migrations:

php artisan migrate

4️⃣ Install JWT Authentication Package

Install tymon/jwt-auth package:

composer require tymon/jwt-auth

Publish the configuration:

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

Do NOT run php artisan jwt:secret since we use RS256, not HS256.

5️⃣ Generate RSA Public & Private Keys

Create a folder to store keys:

mkdir -p storage/jwt

Generate private key (2048 bits):

openssl genrsa -out storage/jwt/private.pem 2048

Generate public key:

openssl rsa -in storage/jwt/private.pem -pubout -out storage/jwt/public.pem

Your structure should look like:

storage/jwt/ ├── private.pem (Keep secret! Do NOT commit to GitHub) └── public.pem (Safe to share)

6️⃣ Configure JWT to Use RS256

Edit config/jwt.php:

'algorithm' => 'RS256', 'keys' => [ 'public' => env('JWT_PUBLIC_KEY'), 'private' => env('JWT_PRIVATE_KEY'), 'passphrase' => env('JWT_PASSPHRASE'), ],

7️⃣ Add JWT Keys to .env

Add these lines to your .env:

JWT_PUBLIC_KEY="file://storage/jwt/public.pem" JWT_PRIVATE_KEY="file://storage/jwt/private.pem" JWT_PASSPHRASE=null

8️⃣ Configure API Auth Guard

Edit config/auth.php and set the api guard to use jwt driver:

'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],

9️⃣ Update User Model

Edit app/Models/User.php to implement JWTSubject:

use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { // Return the identifier to be stored in the JWT subject claim public function getJWTIdentifier() { return $this->getKey(); } // Return any custom claims to be added to the JWT public function getJWTCustomClaims() { return []; } }

🔟 Create Authentication Controller

Generate controller:

php artisan make:controller Api/AuthController

Edit app/Http/Controllers/Api/AuthController.php:

<?php namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class AuthController extends Controller { private string $privateKey; public function __construct() { $this->privateKey = file_get_contents(storage_path('jwt/private.pem')); if (!$this->privateKey) { throw new \Exception('Private key not found or not readable'); } } // Register new user public function register(Request $request) { $data = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', 'password' => 'required|string|min:6|confirmed', ]); User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); return response()->json([ 'message' => 'User registered successfully' ], 201); } // Login user - expects RSA-encrypted password base64 encoded public function login(Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required|string', ]); $encryptedPassword = base64_decode($request->password); $decryptedPassword = null; if (!openssl_private_decrypt($encryptedPassword, $decryptedPassword, $this->privateKey)) { return response()->json(['message' => 'Password decryption failed'], 400); } if (!Auth::attempt(['email' => $request->email, 'password' => $decryptedPassword])) { return response()->json(['message' => 'Invalid credentials'], 401); } $user = Auth::user(); $token = auth('api')->login($user); return response()->json([ 'user' => $user, 'access_token' => $token, 'token_type' => 'Bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60, ]); } // Return authenticated user profile public function profile() { return response()->json(auth('api')->user()); } // Logout user (invalidate JWT token) public function logout() { auth('api')->logout(); return response()->json([ 'message' => 'Logged out successfully' ]); } }

1️⃣1️⃣ Define API Routes

Edit routes/api.php:

use App\Http\Controllers\Api\AuthController; Route::prefix('auth')->group(function () { Route::post('/register', [AuthController::class, 'register']); Route::post('/login', [AuthController::class, 'login']); Route::middleware('auth:api')->group(function () { Route::get('/profile', [AuthController::class, 'profile']); Route::post('/logout', [AuthController::class, 'logout']); }); });

▶️ Run the Application

php artisan serve

API base URL: http://localhost:8000

🚀 How to Use the API (Postman)

1️⃣ Register User

POST /api/auth/register

Body (JSON):

{ "name": "StarCode Kh", "email": "starcodekh@example.com", "password": "12345678", "password_confirmation": "12345678" }

2️⃣ Login User

POST /api/auth/login

Body (JSON):

{ "email": "starcodekh@example.com", "password": "ENCRYPTED_PASSWORD_BASE64" }

Replace ENCRYPTED_PASSWORD_BASE64 with your frontend RSA-encrypted password (base64 encoded).

Response:

{ "user": {...}, "access_token": "token_here", "token_type": "Bearer", "expires_in": 3600 }

3️⃣ Get Profile

GET /api/auth/profile

Headers:

Authorization: Bearer {access_token}

4️⃣ Logout

POST /api/auth/logout

Headers:

Authorization: Bearer {access_token}

🔁 Authentication Flow Summary

  1. Register → User created with bcrypt hashed password

  2. Login → Password encrypted with RSA public key on frontend, decrypted with private key on backend

  3. JWT token issued using RS256

  4. Access protected routes using JWT Bearer token

  5. Logout invalidates token

✅ Production Best Practices

  • Always use HTTPS

  • Keep private keys secure (never commit private.pem)

  • Use short-lived JWT tokens

  • Implement refresh tokens (optional)

  • Rotate RSA keys periodically

🎯 You now have a secure, enterprise-ready Laravel authentication system using:

  • 🔐 RSA encryption

  • 🔑 RS256 JWT signing

  • 🛡 Laravel API best practices

Full Laravel JWT Authentication with RSA (RS256) Source Code
You can find the complete project and source code on GitHub here:
https://github.com/StarCodeKh/Laravel-JWT-Authentication-with-RSA-RS256

This repository contains the full Laravel project with:

  • JWT Authentication using RS256 (RSA public/private keys)

  • RSA password encryption for secure login

  • Secure register, login, profile, and logout API endpoints

  • Proper API middleware and token-based authentication setup

  • Best practices for production-ready Laravel APIs

Feel free to clone, explore, and adapt the code for your own projects.

Souy Soeng

Souy Soeng

Hi there 👋, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
🌱 I’m currently creating a sample Laravel and React Vue Livewire
👯 I’m looking to collaborate on open-source PHP & JavaScript projects
💬 Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
close