{"id":27103034,"url":"https://github.com/yoanesber/spring-boot-jwt-auth-postgresql","last_synced_at":"2026-04-29T00:34:49.076Z","repository":{"id":283320872,"uuid":"945290231","full_name":"yoanesber/Spring-Boot-JWT-Auth-PostgreSQL","owner":"yoanesber","description":"This REST API for managing Netflix Shows is built with Spring Boot, using PostgreSQL, Spring Data JPA, and Spring Security. It secures endpoints with JWT via JJWT, enabling stateless authentication. JWTs act as Bearer tokens, offering scalability over sessions and flexibility over API keys with built-in expiration and claim support.","archived":false,"fork":false,"pushed_at":"2025-08-03T10:43:28.000Z","size":231,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-03T12:18:37.691Z","etag":null,"topics":["auth","authentication","authorization","jwt","postgresql","rest-api","spring-boot","token"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yoanesber.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-03-09T04:29:58.000Z","updated_at":"2025-08-03T10:43:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"a6017575-7725-4176-9d78-b6d45e87b2a8","html_url":"https://github.com/yoanesber/Spring-Boot-JWT-Auth-PostgreSQL","commit_stats":null,"previous_names":["yoanesber/spring-boot-jwt-auth-postgresql"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/yoanesber/Spring-Boot-JWT-Auth-PostgreSQL","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoanesber%2FSpring-Boot-JWT-Auth-PostgreSQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoanesber%2FSpring-Boot-JWT-Auth-PostgreSQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoanesber%2FSpring-Boot-JWT-Auth-PostgreSQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoanesber%2FSpring-Boot-JWT-Auth-PostgreSQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yoanesber","download_url":"https://codeload.github.com/yoanesber/Spring-Boot-JWT-Auth-PostgreSQL/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yoanesber%2FSpring-Boot-JWT-Auth-PostgreSQL/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32405901,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"ssl_error","status_checked_at":"2026-04-28T19:37:55.688Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["auth","authentication","authorization","jwt","postgresql","rest-api","spring-boot","token"],"created_at":"2025-04-06T16:38:34.361Z","updated_at":"2026-04-29T00:34:49.064Z","avatar_url":"https://github.com/yoanesber.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JWT Authentication Demo\n\n## 📖 Overview  \n\nThis project is a **REST API** for managing **Netflix Shows**, built using **Spring Boot**. It leverages **PostgreSQL** as the database, **Spring Data JPA** for data management, and **Spring Boot Starter Security** for authentication and authorization. The API is secured using **JWT (JSON Web Token)**, implemented with the open-source **JJWT** library.  \n\nA key aspect of this project is the implementation of **JJWT** to **create and verify JWTs** as an authentication mechanism for accessing NetflixShows resources. JWT is used as a Bearer token, meaning it is included in the Authorization header of HTTP requests to authenticate users. Compared to traditional session-based authentication, JWT provides a **stateless and scalable** approach, reducing the need for server-side session storage. Unlike API keys, JWTs offer **built-in expiration** and **can carry claims**, allowing for **more flexible authorization strategies**.  \n\n### 🔐 JWT Algorithm Flexibility  \n\nThis application supports both `HMAC` and `RSA` algorithms for **signing JWTs**, allowing you to choose the desired `cryptographic` method based on your security and deployment needs:  \n\n- **HMAC (symmetric)**: Uses a shared secret key (`HS256`).  \n- **RSA (asymmetric)**: Uses a private key to sign and a public key to verify (`RS256`).  \n\nThe signing algorithm is configurable via the `application.properties` file using the property:  \n\n```properties\njwt.key-algorithm=HMAC # or RSA\n```  \n\nThis design provides the flexibility to switch algorithms without changing the application logic.  \n\nThis application functions both as a **resource server** and a **custom authorization server**, as it is responsible for **issuing (access and refresh tokens) and validating JWTs** internally for authenticated users. It implements **custom JWT-based authentication**, meaning it does not follow the full OAuth2 protocol. As a result, the login request only requires a username and password—**the `grant_type` parameter is not needed**—because the token issuance (access and refresh tokens) and token refresh are handled via **separate, dedicated endpoints:**  \n\n- `/auth/login` — Handles user authentication. The user provides a username and password, which are authenticated using `UsernamePasswordAuthenticationToken`. Upon successful authentication, the system sets the authentication object in the `SecurityContextHolder`, generates a JWT access token and a refresh token, and updates the user's last login time.  \n- `/auth/refresh-token` — Manages refresh tokens using a rotating strategy. Refresh tokens are stored in the `refresh_token` table (fields: `token`, `expiry_date`, and `user_id`). When a request is made to this endpoint, the system verifies the token's existence and expiration, then generates a new JWT access token and a new refresh token, replacing the old one.  \n\n### 🔄 Refresh Token Flow\n\nThe **Refresh Token API** is used to **renew** an expired access token without requiring the user to log in again. If the **access token is expired**, but the **refresh token is still valid**, the system will automatically:\n\n- Generate a **new access token**  \n- Generate a **new refresh token**  \n- Replace the old refresh token in the database\n\nThis is a **rotating refresh token strategy** — meaning the old refresh token is replaced after each use to enhance security.\n\nBy keeping authentication and token renewal separate, the design promotes clarity, maintainability, and security, all while maintaining full control over the authentication lifecycle.  \n\n---\n\n## 🤖 Tech Stack  \n\nThe technology used in this project are:  \n\n| Dependency                      | Description                                                                 |\n|---------------------------------|-----------------------------------------------------------------------------|\n| **Spring Boot Starter Web**     | Building RESTful APIs or web applications                                   |\n| **Spring Security**             | Provides authentication and authorization mechanisms for secure access      |\n| **JJWT (api, impl, jackson)**   | Library for creating and verifying JSON Web Tokens (JWTs) used in auth      |\n| **PostgreSQL**                  | Serves as the relational database for storing Netflix Shows                 |\n| **Hibernate**                   | Simplifies database interactions via JPA                                    |\n| **Lombok**                      | Reduces boilerplate code (e.g., getters, setters, constructors)             |\n\n---\n\n## 🧱 Architecture Overview  \n\nThe project follows a modular architecture to ensure **separation of concerns**, **testability**, and **maintainability**. Here's a breakdown of each module's responsibility:  \n\n```bash\n📂jwt-auth-demo/\n└── 📂src/\n    └── 📂main/\n        ├── 📂docker/\n        │   ├── 📂app/                     # Dockerfile for Spring Boot application (runtime container)\n        │   │   └── Dockerfile             # Uses base image, copies JAR/dependencies, defines ENTRYPOINT\n        │   └── 📂postgres/                # Custom PostgreSQL Docker image (optional)\n        │       ├── Dockerfile             # Extends from postgres:17, useful for init customization\n        │       └── init.sql               # SQL script to create database, user, and grant permissions\n        ├── 📂java/\n        │   ├── 📂config/                  # Spring configuration classes (e.g., security, serializer)\n        │   │   ├── 📂security/            # Security-related configuration (cors, jwt)\n        │   │   ├── 📂serializer/          # Custom Jackson serializers/deserializers (e.g., for `Instant`)\n        │   │   └── 📂shutdown/            # Custom shutdown logic (e.g., for graceful shutdown)\n        │   ├── 📂controller/              # REST API endpoints (e.g., AuthController, NetflixShowsController)\n        │   ├── 📂dto/                     # Data Transfer Objects for requests/responses\n        │   ├── 📂entity/                  # JPA entity classes mapped to database tables\n        │   ├── 📂handler/                 # Global exception handling and custom error responses\n        │   ├── 📂mapper/                  # MapStruct or manual mappers between DTO and entity\n        │   ├── 📂repository/              # Spring Data JPA interfaces for database access\n        │   ├── 📂service/                 # Business logic layer\n        │   │   └── 📂impl/                # Service implementation classes\n        │   └── 📂util/                    # Utility/helper classes (e.g., JWT helpers, response builder, security util)\n        └── 📂resources/\n            ├── application.properties     # Application configuration (DB, JWT, profiles, etc.)\n            ├── generate-jwt-keys.sh       # Script to generate RSA key pairs for JWT\n            ├── import.sql                 # SQL file for seeding database on startup\n            ├── privateKey.pem             # RSA private key for signing JWTs\n            └── publicKey.pem              # RSA public key for verifying JWTs\n```\n\nThis clean separation allows the application to **scale well**, supports **test-driven development**, and adheres to best practices in **enterprise application design**.  \n\n---\n\n\n## 🛠️ Installation \u0026 Setup  \n\nFollow these steps to set up and run the project locally:  \n\n### ✅ Prerequisites\n\nMake sure the following tools are installed on your system:\n\n| Tool                                      | Description                                                                 | Required      |\n|-------------------------------------------|-----------------------------------------------------------------------------|---------------|\n| [Java 17+](https://adoptium.net/)         | Java Development Kit (JDK) to run the Spring application                   | ✅            |\n| [PostgreSQL](https://www.postgresql.org/) | Relational database to persist application data                             | ✅            |\n| [Make](https://www.gnu.org/software/make/)| Automation tool for tasks like `make run-app`                               | ✅            |\n| [Docker](https://www.docker.com/)         | To run services like PostgreSQL in isolated containers                      | ⚠️ *optional* |\n\n### ☕ 1. Install Java 17  \n\n1. Ensure **Java 17** is installed on your system. You can verify this with:  \n\n```bash\njava --version\n```  \n\n2. If Java is not installed, follow one of the methods below based on your operating system:  \n\n#### 🐧 Linux  \n\n**Using apt (Ubuntu/Debian-based)**:  \n\n```bash\nsudo apt update\nsudo apt install openjdk-17-jdk\n```  \n\n#### 🪟 Windows  \n1. Use [https://adoptium.net](https://adoptium.net) to download and install **Java 17 (Temurin distribution recommended)**.  \n\n2. After installation, ensure `JAVA_HOME` is set correctly and added to the `PATH`.  \n\n3. You can check this with:  \n\n```bash\necho $JAVA_HOME\n```  \n\n### 🐘 2. Install PostgreSQL  \n1. Install PostgreSQL if it’s not already available on your machine:  \n    - Use [https://www.postgresql.org/download/](https://www.postgresql.org/download/) to download PostgreSQL.  \n\n2. Once installed, create the following databases:  \n```sql\nCREATE DATABASE netflix;  \n```  \n\nThese databases are used for development and automated testing, respectively.  \n\n### 🧰 3. Install `make` (Optional but Recommended)  \nThis project uses a `Makefile` to streamline common tasks.  \n\nInstall `make` if not already available:  \n\n#### 🐧 Linux  \n\nInstall `make` using **APT**  \n\n```bash\nsudo apt update\nsudo apt install make\n```  \n\nYou can verify installation with:   \n```bash\nmake --version\n```  \n\n#### 🪟 Windows  \n\nIf you're using **PowerShell**:  \n\n- Install [Chocolatey](https://chocolatey.org/install) (if not installed):  \n```bash\nSet-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))\n```  \n\n- Verify `Chocolatey` installation:  \n```bash\nchoco --version\n```  \n\n- Install `make` via `Chocolatey`:  \n```bash\nchoco install make\n```  \n\nAfter installation, **restart your terminal** or ensure `make` is available in your `PATH`.  \n\n### 🔁 4. Clone the Project  \n\nClone the repository:  \n\n```bash\ngit clone https://github.com/yoanesber/Spring-Boot-JWT-Auth-PostgreSQL.git\ncd Spring-Boot-JWT-Auth-PostgreSQL\n```  \n\n### ⚙️ 5. Configure Application Properties  \n\nSet up your `application.properties` in `src/main/resources`:  \n\n```properties\n# application configuration\nspring.application.name=jwt-auth-demo\nserver.port=8080\nspring.profiles.active=development\n\n## datasource configuration\nspring.datasource.url=jdbc:postgresql://localhost:5432/netflix\nspring.datasource.username=appuser\nspring.datasource.password=app@123\nspring.datasource.driver-class-name=org.postgresql.Driver\nspring.sql.init.mode=always\n\n## hibernate configuration\nspring.jpa.show-sql=true\nspring.jpa.hibernate.ddl-auto=create-drop\nspring.jpa.properties.hibernate.format_sql=true\nspring.jpa.open-in-view=true\n\n\n## jwt configuration\njwt.header=Authorization\njwt.token.type=Bearer\njwt.issuer=http://localhost:8080/auth/login\njwt.expiration-ms=900000\njwt.refresh-token.expiration-ms=1296000000\njwt.cookie.name=accessToken\njwt.cookie.path=/api\njwt.cookie.max-age-ms=86400000\njwt.cookie.secure=true\njwt.cookie.http-only=true\njwt.cookie.same-site=Lax\njwt.cookie.response-enabled=true\njwt.key-algorithm=HMAC\n# optional: if you want to use asymmetric encryption (RSA)\njwt.private-key-file=./src/main/resources/privateKey.pem\njwt.public-key-file=./src/main/resources/publicKey.pem\njwt.keySize=2048\n# optional: if you want to use symmetric encryption (HMAC)\njwt.key-secret=a-string-secret-at-least-256-bits-long\n\n## cors configuration\ncors-allowed-origins=http://localhost:8082,http://localhost:8083,https://jwt-auth-postgres:8082,https://jwt-auth-postgres:8083\ncors-allowed-methods=GET,POST,PUT,DELETE,OPTIONS\ncors-allowed-headers=Authorization,Cache-Control,Content-Type\ncors-exposed-headers=Authorization,Content-Length\ncors-configuration-endpoint=/**\ncors-allow-credentials=true\ncors-max-age=3600\n\n## http security\npermit-all-request-url=/api/v1/auth/**\nexcluded-paths-for-authentication=/api/v1/auth/login,/api/v1/auth/refresh-token\n```\n\n- **🔐 Notes**:  Ensure that:  \n  - Database URLs, username, and password are correct.  \n  - JWT keys (path to `.pem` files) are set correctly.\n  - `spring.datasource.username=appuser`, `spring.datasource.password=app@123`: It's strongly recommended to create a dedicated database user instead of using the default postgres superuser.\n\n\n\n### 🔐 6. Generate JWT RSA Key Pair  \n\nGenerate a `private` and `public key` pair to **sign** and **verify** JWT tokens:  \n\n**Using `make`:**  \n\n```bash\nmake generate-jwt-keys\n```  \n\n**Or manually:**  \n\n```bash\nbash generate-jwt-keys.sh\n```  \n\nThis will generate `privateKey.pem` and `publicKey.pem` in the `src/main/resources/` directory. And the files will be referenced by your `application.properties`:\n```properties\njwt.private-key-file=./src/main/resources/privateKey.pem\njwt.public-key-file=./src/main/resources/publicKey.pem\n```\n\n**⚠️ Security Note:**  \nThe `privateKey.pem` file is included in `.gitignore` to **prevent accidental commits to the repository**, especially since this project will be made **public**.  \n**Never expose your private key** in version control to protect your JWT signing mechanism. You **must generate** your own `private` and `public key` pair.  \n\n\n### 👤 7. Create Dedicated PostgreSQL User (Recommended)\n\nFor security reasons, it's recommended to avoid using the default postgres superuser. Use the following SQL script to create a dedicated user (`appuser`) and assign permissions:\n\n```sql\n-- Create appuser and database\nCREATE USER appuser WITH PASSWORD 'app@123';\n\n-- Allow user to connect to database\nGRANT CONNECT ON DATABASE netflix TO appuser;\n\n-- Grant permissions on public schema\nGRANT USAGE, CREATE ON SCHEMA public TO appuser;\n\n-- Grant all permissions on existing tables\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO appuser;\n\n-- Grant all permissions on sequences (if using SERIAL/BIGSERIAL ids)\nGRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO appuser;\n\n-- Ensure future tables/sequences will be accessible too\nALTER DEFAULT PRIVILEGES IN SCHEMA public\nGRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO appuser;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA public\nGRANT USAGE, SELECT, UPDATE ON SEQUENCES TO appuser;\n```\n\nUpdate your `application.properties` accordingly:\n```properties\nspring.datasource.username=appuser\nspring.datasource.password=app@123\n```\n\n## 🚀 8. Running the Application  \n\nThis section provides step-by-step instructions to run the application either **locally** or via **Docker containers**.\n\n- **Notes**:  \n  - All commands are defined in the `Makefile`.\n  - To run using `make`, ensure that `make` is installed on your system.\n  - To run the application in containers, make sure `Docker` is installed and running.\n\n\n### 🔧 Run Locally (Non-containerized)\n\nEnsure PostgreSQL are running locally, then:\n\n```bash\nmake dev\n```\n\n### 🐳 Run Using Docker\n\nTo build and run all services (PostgreSQL, Spring app):\n\n```bash\nmake docker-start-all\n```\n\nTo stop and remove all containers:\n\n```bash\nmake docker-stop-all\n```\n\n- **Notes**:  \n  - Before running the application inside Docker, make sure to update your `application.properties`\n    - Replace `localhost` with the appropriate **container name** for services like PostgreSQL.  \n    - For example:\n      - Change `localhost:5432` to `postgres-server:5432`\n\n### 🟢 Application is Running\n\nNow your application is accessible at:\n```bash\nhttp://localhost:8080\n```\n\n---\n\n\n## 🧪 Testing Scenarios  \n\nThe REST API provides a set of endpoints to manage Netflix shows, allowing clients to perform CRUD operations (Create, Read, Update, Delete). Each endpoint follows RESTful principles and accepts/returns JSON data. Authentication is handled using JWT Bearer tokens, ensuring secure access to protected resources. Below is a list of available endpoints along with sample requests.  \n\n### 🔐 Authentication  \n\n#### 1. Login API Testing Scenarios\n\nLogin API allows users to authenticate by providing valid credentials. Upon successful authentication, the server responds with an access token and a refresh token. The access token is used for making authorized requests, while the refresh token is used to obtain a new access token when the previous one expires.  \n\n**Endpoint:**  \n\n```bash\nPOST http://localhost:8080/auth/login\nContent-Type: application/json\n```  \n\n##### Scenario 1: Successful Login\n\n**Request Body:**  \n\n```json\n{\n    \"username\":\"userone\",\n    \"password\":\"P@ssw0rd\"\n}\n```\n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Login successful\",\n    \"error\": null,\n    \"path\": \"/api/v1/auth/login\",\n    \"status\": 200,\n    \"data\": {\n        \"accessToken\": \"\u003cJWT_TOKEN\u003e\",\n        \"refreshToken\": \"\u003cUUID_REFRESH_TOKEN\u003e\",\n        \"expirationDate\": \"2025-05-28T12:29:51.000Z\",\n        \"tokenType\": \"Bearer\"\n    },\n    \"timestamp\": \"2025-05-28T12:14:51.684714Z\"\n}\n```\n\n\n##### Scenario 2: Invalid Credentials  \n\n**Request Body:**  \n\n```json\n{\n    \"username\":\"invalid_user\",\n    \"password\":\"P@ssw0rd\"\n}\n```  \n\n**Expected Response (`401 Unauthorized`):**  \n\n```json\n{\n    \"message\": \"Authentication Failed\",\n    \"error\": \"Invalid username or password\",\n    \"path\": \"/auth/login\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:26:37.289781300Z\"\n}\n```  \n\n##### Scenario 3: Disabled User  \n\n**Precondition:**  \n\n```sql\nUPDATE users SET is_enabled = false WHERE id = 2;\n```  \n\n**Request Body:**  \n\n```json\n{\n    \"username\":\"userone\",\n    \"password\":\"P@ssw0rd\"\n}\n```  \n\n**Expected Response (`401 Unauthorized`):**  \n\n```json\n{\n    \"message\": \"Authentication Failed\",\n    \"error\": \"User is disabled\",\n    \"path\": \"/auth/login\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:27:59.157329700Z\"\n}\n```  \n\n##### Scenario 4: Expired User Account  \n\n**Precondition:**  \n\n```sql\nUPDATE users SET is_account_non_expired = false WHERE id = 2;\n```  \n\n**Request Body:**  \n\n```json\n{\n    \"username\":\"userone\",\n    \"password\":\"P@ssw0rd\"\n}\n```  \n\n**Expected Response (`401 Unauthorized`):**  \n\n```json\n{\n    \"message\": \"Authentication Failed\",\n    \"error\": \"User account has expired\",\n    \"path\": \"/auth/login\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:29:01.301251900Z\"\n}\n```  \n\n#### 🔄 Refresh Token API  \n\nRefresh Token API is used to **renew** an expired access token without requiring the user to log in again. Clients send a valid refresh token, and the server issues a new access token and a new refresh token.  \n\n**Endpoint:**  \n\n```bash\nPOST http://localhost:8080/auth/refresh-token\nContent-Type: application/json\n```  \n\n##### Scenario 1: Successful Refresh Token\n\n**Request Body:**  \n\n```json\n{\n    \"refreshToken\": \"\u003cUUID_REFRESH_TOKEN\u003e\"\n}\n```\n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Refresh token successful\",\n    \"error\": null,\n    \"path\": \"/auth/refresh-token\",\n    \"status\": 200,\n    \"data\": {\n        \"accessToken\": \"\u003cJWT_TOKEN\u003e\",\n        \"refreshToken\": \"\u003cUUID_REFRESH_TOKEN\u003e\",\n        \"expirationDate\": \"2025-05-28T12:30:07.000Z\",\n        \"tokenType\": \"Bearer\"\n    },\n    \"timestamp\": \"2025-05-28T12:15:07.486443700Z\"\n}\n```\n\n##### Scenario 2: Invalid Refresh Token\n\n**Invalid refresh token Response:**  \n\n```json\n{\n    \"message\": \"Invalid Refresh Token\",\n    \"error\": \"The provided refresh token is invalid or does not exist\",\n    \"path\": \"/auth/refresh-token\",\n    \"status\": 400,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:33:20.291114800Z\"\n}\n```\n\n\n### 🎬 Netflix Shows API  \n\nNetflix Show API allows users to perform CRUD operations on Netflix Shows. Users can create, retrieve, update, and delete show records. Access to these endpoints requires authentication via JWT.  \n\n#### Create a Netflix Show  \n\nThis endpoint allows users to create a new Netflix show by providing relevant details in the request body. Ensure that a valid JWT token is included in the Authorization header.  \n\n**Endpoint:**  \n\n```bash\nPOST http://localhost:8080/api/v1/netflix-shows\nContent-Type: application/json\nAuthorization: Bearer \u003cJWT_TOKEN\u003e\n```  \n\n##### Scenario 1: Successful Create Record\n\n**Request Body:**  \n\n```json\n{\n    \"showType\": \"MOVIE\",\n    \"title\": \"Sankofa\",\n    \"director\": \"Haile Gerima\",\n    \"castMembers\": \"Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra Duah, Nick Medley, Mutabaruka, Afemo Omilami, Reggie Carter, Mzuri, Oliver\",\n    \"country\": \"United States\",\n    \"dateAdded\": \"2021-09-24\",\n    \"releaseYear\": 2024,\n    \"rating\": \"TV-MA\",\n    \"duration\": \"90 min\",\n    \"listedIn\": \"Drama\",\n    \"description\": \"A woman adjusting to life after a loss contends with a feisty bird that's taken over her garden — and a husband who's struggling to find a way forward.\"\n}\n```\n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Record created successfully\",\n    \"error\": null,\n    \"path\": \"/api/v1/netflix-shows\",\n    \"status\": 200,\n    \"data\": {\n        \"id\": 21,\n        \"showType\": \"MOVIE\",\n        \"title\": \"Sankofa\",\n        \"director\": \"Haile Gerima\",\n        \"castMembers\": \"Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra Duah, Nick Medley, Mutabaruka, Afemo Omilami, Reggie Carter, Mzuri, Oliver\",\n        \"country\": \"United States\",\n        \"dateAdded\": \"2021-09-24\",\n        \"releaseYear\": 2024,\n        \"rating\": \"TV-MA\",\n        \"duration\": \"90 min\",\n        \"listedIn\": \"Drama\",\n        \"description\": \"A woman adjusting to life after a loss contends with a feisty bird that's taken over her garden — and a husband who's struggling to find a way forward.\"\n    },\n    \"timestamp\": \"2025-05-28T15:40:08.232535600Z\"\n}\n```\n\n##### Scenario 2: Expired Token\n\n**Expired JWT Token Response:**  \n\n```json\n{\n    \"message\": \"Unauthorized request\",\n    \"error\": \"Token has expired\",\n    \"path\": \"/api/v1/netflix-shows\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:37:01.115671900Z\"\n}\n```\n\n##### Scenario 2: Invalid Token\n\n**Invalid JWT Token Response:**  \n\n```json\n{\n    \"message\": \"Unauthorized request\",\n    \"error\": \"Invalid token signature\",\n    \"path\": \"/api/v1/netflix-shows\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:38:21.516094300Z\"\n}\n```\n\n##### Scenario 3: Malformed Token\n\n**Malformed JWT Token Response:**  \n\n```json\n{\n    \"message\": \"Unauthorized request\",\n    \"error\": \"Malformed or unsupported JWT token\",\n    \"path\": \"/api/v1/netflix-shows\",\n    \"status\": 401,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:38:34.345061600Z\"\n}\n```\n\n#### Get All Netflix Shows  \n\nRetrieves a list of all Netflix shows stored in the database.  \n\n**Endpoint:**  \n\n```bash\nGET http://localhost:8080/api/v1/netflix-shows\nAuthorization: Bearer \u003cJWT_TOKEN\u003e\n```  \n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Record retrieved successfully\",\n    \"error\": null,\n    \"path\": \"/api/v1/netflix-shows\",\n    \"status\": 200,\n    \"data\": [\n        {\n            \"id\": 1,\n            \"showType\": \"MOVIE\",\n            \"title\": \"Dick Johnson Is Dead\",\n            \"director\": \"Kirsten Johnson\",\n            \"castMembers\": null,\n            \"country\": \"United States\",\n            \"dateAdded\": \"2021-09-25\",\n            \"releaseYear\": 2020,\n            \"rating\": \"PG-13\",\n            \"duration\": \"90 min\",\n            \"listedIn\": \"Documentaries\",\n            \"description\": \"As her father nears the end of his life, filmmaker Kirsten Johnson stages his death in inventive and comical ways to help them both face the inevitable.\"\n        },\n        ...\n    ],\n    \"timestamp\": \"2025-05-28T15:42:21.627460400Z\"\n}\n```\n\n#### Get Netflix Show by ID  \n\nFetches the details of a specific Netflix show using its unique ID.  \n\n**Endpoint:**  \n\n```bash\nGET http://localhost:8080/api/v1/netflix-shows/{id}  \nAuthorization: Bearer \u003cJWT_TOKEN\u003e\n```  \n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Record retrieved successfully\",\n    \"error\": null,\n    \"path\": \"/api/v1/netflix-shows/21\",\n    \"status\": 200,\n    \"data\": {\n        \"id\": 21,\n        \"showType\": \"MOVIE\",\n        \"title\": \"Sankofa\",\n        \"director\": \"Haile Gerima\",\n        \"castMembers\": \"Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra Duah, Nick Medley, Mutabaruka, Afemo Omilami, Reggie Carter, Mzuri, Oliver\",\n        \"country\": \"United States\",\n        \"dateAdded\": \"2021-09-24\",\n        \"releaseYear\": 2024,\n        \"rating\": \"TV-MA\",\n        \"duration\": \"90 min\",\n        \"listedIn\": \"Drama\",\n        \"description\": \"A woman adjusting to life after a loss contends with a feisty bird that's taken over her garden — and a husband who's struggling to find a way forward.\"\n    },\n    \"timestamp\": \"2025-05-28T15:43:40.840925500Z\"\n}\n```\n\n**Not found Response:**  \n\n```json\n{\n    \"message\": \"Record not found\",\n    \"error\": \"NetflixShows not found with ID: 22\",\n    \"path\": \"/api/v1/netflix-shows/22\",\n    \"status\": 404,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:46:07.050397400Z\"\n}\n```\n\n#### Update a Netflix Show  \n\nAllows updating the details of an existing Netflix show.  \n\n**Endpoint:**  \n\n```bash\nPUT http://localhost:8080/api/v1/netflix-shows/{id}  \nContent-Type: application/json\nAuthorization: Bearer \u003cJWT_TOKEN\u003e\n```  \n\n**Request Body:**  \n\n```json\n{\n    \"showType\": \"MOVIE\",\n    \"title\": \"Sankofa\",\n    \"director\": \"Haile Gerima\",\n    \"castMembers\": \"Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra Duah, Nick Medley, Mutabaruka, Afemo Omilami, Reggie Carter, Mzuri, Oliver\",\n    \"country\": \"United States\",\n    \"dateAdded\": \"2021-09-24\",\n    \"releaseYear\": 2024,\n    \"rating\": \"TV-MB\",\n    \"duration\": \"120 min\",\n    \"listedIn\": \"Comedy\",\n    \"description\": \"A woman adjusting to life after a loss contends with a feisty bird that's taken over her garden — and a husband who's struggling to find a way forward.\"\n}\n```\n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Record updated successfully\",\n    \"error\": null,\n    \"path\": \"/api/v1/netflix-shows/21\",\n    \"status\": 200,\n    \"data\": {\n        \"id\": 21,\n        \"showType\": \"MOVIE\",\n        \"title\": \"Sankofa\",\n        \"director\": \"Haile Gerima\",\n        \"castMembers\": \"Kofi Ghanaba, Oyafunmike Ogunlano, Alexandra Duah, Nick Medley, Mutabaruka, Afemo Omilami, Reggie Carter, Mzuri, Oliver\",\n        \"country\": \"United States\",\n        \"dateAdded\": \"2021-09-24\",\n        \"releaseYear\": 2024,\n        \"rating\": \"TV-MB\",\n        \"duration\": \"120 min\",\n        \"listedIn\": \"Comedy\",\n        \"description\": \"A woman adjusting to life after a loss contends with a feisty bird that's taken over her garden — and a husband who's struggling to find a way forward.\"\n    },\n    \"timestamp\": \"2025-05-28T12:15:49.740526700Z\"\n}\n```\n\n**Not found Response:**  \n\n```json\n{\n    \"message\": \"Record not found\",\n    \"error\": \"NetflixShows not found with ID: 22\",\n    \"path\": \"/api/v1/netflix-shows/22\",\n    \"status\": 404,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T15:45:31.568490900Z\"\n}\n```\n\n#### Delete a Netflix Show  \n\nDeletes a specific Netflix show from the database.  \n\n**Note**:  \nThis operation performs a soft delete, meaning the record is not permanently removed from the database. Instead, it updates the following fields in the database:  \n- `is_deleted` → set to true\n- `deleted_by` → set to the current authenticated user ID\n- `deleted_at` → set to the current timestamp\n\nThese flags allow the record to be excluded from retrieval operations (e.g., Get All, Get By ID), but still retained in the database for audit or recovery purposes.\nAll `GET` endpoints are designed to exclude records where `is_deleted = true`.\n\n**Endpoint:**  \n\n```bash\nDELETE http://localhost:8080/api/v1/netflix-shows/{id}  \nAuthorization: Bearer \u003cJWT_TOKEN\u003e\n```  \n\n**Successful Response:**  \n\n```json\n{\n    \"message\": \"Record deleted successfully\",\n    \"error\": null,\n    \"path\": \"/api/v1/netflix-shows/21\",\n    \"status\": 200,\n    \"data\": null,\n    \"timestamp\": \"2025-05-28T12:15:56.640090800Z\"\n}\n```\n---\n\n## 📝 Notes \u0026 Future Enhancements  \n\n- **JWT Expiration** – The access token has a limited validity period. Clients should use the refresh token to obtain a new access token when expired.\n- **Authorization** – Every API request must include a valid JWT token in the Authorization header (`Bearer \u003cJWT Token\u003e`).\n- **Stateless or Stateful Authentication**: When implementing authentication with JWT, it's important to consider whether to use a stateless or stateful approach based on the application's needs.\n- **Data Validation** – Requests with missing or invalid fields will return a `400 Bad Request` response.\n- **Security Considerations**:\n    - Never expose JWT tokens in frontend code or logs.\n    - Use HTTPS to protect tokens in transit.\n    - Implement `role-based access control (RBAC)` to restrict API actions based on user roles, ensuring that only authorized users can perform specific operations. Assign different roles such as 'ADMIN' and 'USER' to enforce proper access levels.\n- **Database Schema** – Ensure all necessary tables (`users, roles, netflix_shows`) are created and populated correctly.\n- **Error Handling** – The API provides meaningful error messages with appropriate HTTP status codes (`400, 401, 403, 404, 500`).\n\n---\n\n## 🔗 Related Repositories  \n\n- JWT Authentication with Kong GitHub Repository, check out [Spring Boot Department API with Kong JWT Authentication (DB-Backed Mode)](https://github.com/yoanesber/Spring-Boot-JWT-Auth-Kong).  \n- Form-Based Authentication Repository, check out [Spring Web Application with JDBC Session](https://github.com/yoanesber/Spring-Boot-JDBC-Session).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoanesber%2Fspring-boot-jwt-auth-postgresql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyoanesber%2Fspring-boot-jwt-auth-postgresql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyoanesber%2Fspring-boot-jwt-auth-postgresql/lists"}