---
url: 'https://www.corbado.com/blog/nodejs-express-postgresql-jwt-authentication-roles'
title: 'Node.js Express PostgreSQL Authentication with JWT & Roles'
description: 'Learn how to build a secure Node.js Express backend with PostgreSQL authentication, JWT authorization, and role-based access control in this step-by-step guide.'
lang: 'en'
author: 'Lukas'
date: '2025-01-16T07:48:17.229Z'
lastModified: '2026-03-27T07:01:08.657Z'
keywords: 'PostgreSQL authentication, PostgreSQL auth'
category: 'Engineering'
---

# Node.js Express PostgreSQL Authentication with JWT & Roles

In this tutorial, we'll build a modern [Node.js](https://www.corbado.com/blog/nodejs-passkeys) Express REST API
that supports token-based authentication using JWT (JSON Web Token) and
[PostgreSQL](https://www.corbado.com/blog/passkey-webauthn-database-guide). By the end of this guide, you'll
understand:

1. **Comprehensive Flow for User Registration & Login with JWT Authentication**
2. **Modern Node.js Express Architecture with CORS, Authentication & Authorization
   Middlewares, and Sequelize**
3. **Configuration of Express Routes to Work Seamlessly with JWT**
4. **Definition of Data Models and Associations for Robust Authentication and
   Authorization**
5. **Utilization of Sequelize for Efficient Interaction with PostgreSQL Database**

Let's dive in!

## 1. Token-based authentication

### 1.1 How does token-based authentication work?

![token based authentication](https://www.corbado.com/website-assets/token_based_authentication_35a99552df.png)

Unlike session-based authentication, which stores session data on the server and relies on
cookies, token-based authentication stores the JWT on the client side - such as in Local
Storage for browsers, Keychain for [iOS](https://www.corbado.com/blog/webauthn-errors), or SharedPreferences for
[Android](https://www.corbado.com/blog/how-to-enable-passkeys-android). This approach eliminates the need for an
additional backend to support native apps or separate authentication modules.

### 1.2 Attaching JWT to requests

A JWT consists of three parts:

- **Header**
- **Payload**
- **Signature**

These parts are concatenated into a standard structure: `header.payload.signature`.

Clients typically include the JWT in the following headers:

- **Authorization Header with Bearer Prefix:**

    ```
    Authorization: Bearer <token>
    ```

- **Custom Header:**

    ```
    x-access-token: <token>
    ```

## 2. Why use PostgreSQL for authentication in Node.js Express?

[PostgreSQL](https://www.corbado.com/blog/passkey-webauthn-database-guide) is a robust and scalable relational
database that integrates seamlessly with [Node.js](https://www.corbado.com/blog/nodejs-passkeys) and Express for
building secure and efficient backend systems. It offers several benefits for implementing
authentication and authorization:

- **Advanced Data Management:** [PostgreSQL](https://www.corbado.com/blog/passkey-webauthn-database-guide)
  supports relational data models, making it easier to manage user roles, permissions, and
  relationships.
- **Security Features:** Built-in tools like row-level security and encryption ensure
  sensitive data, such as user credentials, remains secure.
- **JSON Support:** [PostgreSQL's](https://www.corbado.com/blog/passkey-webauthn-database-guide) ability to
  handle JSON data simplifies the integration of token-based authentication mechanisms
  like JWT.
- **High Scalability:** With its capability to handle concurrent requests, PostgreSQL is
  ideal for applications with a growing user base.

By leveraging PostgreSQL, you can ensure a scalable and secure solution for role-based
authentication in your [Node.js](https://www.corbado.com/blog/nodejs-passkeys) backend.

## 3. Overview of Node.js JWT Authentication with PostgreSQL

We'll build a Node.js Express application where users can:

- **Sign Up:** Create a new account.
- **Sign In:** Log in with a username and password.
- **Access Resources:** Access public and role-protected resources based on their assigned
  roles (`admin`, `moderator`, `user`).

**Required APIs:**

| Method | URL                | Action                   |
| ------ | ------------------ | ------------------------ |
| POST   | `/api/auth/signup` | Register a new account   |
| POST   | `/api/auth/signin` | Log in to an account     |
| GET    | `/api/test/all`    | Retrieve public content  |
| GET    | `/api/test/user`   | Access user content      |
| GET    | `/api/test/mod`    | Access moderator content |
| GET    | `/api/test/admin`  | Access admin content     |

## 4. How JWT secures Node.js REST APIs with PostgreSQL

JWT (JSON Web Token) is a lightweight, stateless authentication method ideal for REST
APIs. Here's how it integrates with PostgreSQL:

1. **Token Structure:** A JWT contains three parts: the header (algorithm and token type),
   the payload (user information and claims), and the signature (signed with a secret
   key).
2. **Verification:** The server verifies the JWT using the secret key and extracts the
   [user ID](https://www.corbado.com/blog/webauthn-user-id-userhandle). Middleware handles this process:

```javascript
jwt.verify(token, authConfig.secret, (err, decoded) => {
    if (err) return res.status(401).json({ message: "Unauthorized!" });
    req.userId = decoded.id;
    next();
});
```

3. **Role-based access:** PostgreSQL stores user roles, which are checked after JWT
   verification to enforce access restrictions.

This setup ensures that only authorized users can access protected resources.

## 5. Flow for Signup & Login with JWT Authentication

![jwt auth sign up sign in](https://www.corbado.com/website-assets/jwt_auth_sign_up_sign_in_2fd1e6fe49.png)

### 6.1 User Registration

- User submits signup details.
- Server validates and creates a new user with a default role (`user`).

### 6.2 User Login

- User submits login credentials.
- Server verifies credentials and issues a JWT.

### 6.3 Authorization

- User includes JWT in requests to access protected resources.
- Server verifies JWT and authorizes access based on user roles.

**Role-based authorization in Node.js Express using PostgreSQL:**

Role-based authorization allows applications to enforce access control by assigning
specific permissions to users. Here’s how it works in a Node.js Express backend:

1. **Defining roles:** PostgreSQL is used to define roles like admin, moderator, and user.
   These roles are stored in the roles table and linked to users through a many-to-many
   relationship. Example Role Definition:

```javascript
db.role.belongsToMany(db.user, { through: "user_roles" });
db.user.belongsToMany(db.role, { through: "user_roles" });
```

2. **Assigning roles during signup:** When a new user registers, a default role (e.g.,
   user) is assigned. This ensures that every user has a role from the start. Example
   Code:

```javascript
const role = await Role.findOne({ where: { name: "user" } });
await user.setRoles([role]);
```

3. **Authorization middleware:** Middleware is used to check user roles during requests.
   For example:

```javascript
const isAdmin = async (req, res, next) => {
    const user = await User.findByPk(req.userId);
    const roles = await user.getRoles();
    if (roles.some((role) => role.name === "admin")) {
        return next();
    }
    return res.status(403).json({ message: "Require Admin Role!" });
};
```

This structure ensures fine-grained access control, making it easier to secure sensitive
endpoints.

**Security Note:** Ensure that the JWT is securely stored on the client side to prevent
unauthorized access.

You also need to refresh tokens when they expire:

![refresh token](https://www.corbado.com/website-assets/refresh_token_1b1fd5f3d9.png)

## 7. Top benefits of using JWT for Node.js Express authentication

JWT provides several benefits when securing Node.js Express applications:

- **Stateless Authentication:** Unlike session-based systems, JWT does not require
  server-side storage, making it more scalable.
- **Cross-Platform Compatibility:** JWT works seamlessly with web, mobile, and desktop
  applications.
- **Enhanced Security:** Tokens can be signed and encrypted to prevent tampering.
- **Integration with PostgreSQL:** The database can efficiently verify user roles and
  permissions based on the JWT payload.

## 8. Seamless role management in Node.js REST APIs

Managing roles is critical for secure and efficient access control. PostgreSQL simplifies
this with its robust data management capabilities:

1. **Role assignment during user creation:** When a user signs up, they are automatically
   assigned a default role, such as user.

```javascript
const role = await Role.findOne({ where: { name: "user" } });
await user.setRoles([role]);
```

2. **Dynamic role updates:** Admins can modify user roles dynamically via API endpoints:

```javascript
const updateRoles = async (userId, newRoles) => {
    const user = await User.findByPk(userId);
    const roles = await Role.findAll({ where: { name: newRoles } });
    await user.setRoles(roles);
};
```

3. **Role-based middleware:** Middleware checks roles before granting access to specific
   endpoints. For example:

```javascript
const isModerator = async (req, res, next) => {
    const roles = await req.user.getRoles();
    if (roles.some((role) => role.name === "moderator")) {
        return next();
    }
    return res.status(403).json({ message: "Require Moderator Role!" });
};
```

## 9. Modern Node.js Express Architecture with Authentication & Authorization

![react nodejs express postgresql architecture](https://www.corbado.com/website-assets/react_nodejs_express_postgresql_architecture_669d7128f3.png)

Our architecture comprises:

- **Express Routes:** Handle incoming HTTP requests.
- **CORS Middleware:** Manage [Cross-Origin](https://www.corbado.com/blog/iframe-passkeys-webauthn) Resource
  Sharing.
- **Security Layer:**
    - **JWT Authentication Middleware:** Verifies JWTs during signup and token
      verification.
    - **Authorization Middleware:** Checks user roles against database records.
- **Controllers:** Interact with the PostgreSQL database via Sequelize and respond to
  client requests with relevant data or tokens.

## 10. Technology Stack

- **Node.js:** JavaScript runtime.
- **Express:** Web framework for Node.js.
- **Tailwind CSS:** Modern CSS framework (replacing Bootstrap).
- **Sequelize:** Promise-based ORM for Node.js.
- **PostgreSQL:** Relational database system.
- **jsonwebtoken:** For JWT creation and verification.
- **bcryptjs:** For hashing passwords.

## 11. Project Structure

```
node-js-jwt-auth-postgresql/
├── app/
│   ├── config/
│   │   ├── auth.config.js
│   │   └── db.config.js
│   ├── controllers/
│   │   ├── auth.controller.js
│   │   └── user.controller.js
│   ├── middlewares/
│   │   ├── authJwt.js
│   │   └── verifySignUp.js
│   ├── models/
│   │   ├── index.js
│   │   ├── role.model.js
│   │   └── user.model.js
│   └── routes/
│       ├── auth.routes.js
│       └── user.routes.js
├── server.js
├── package.json
└── tailwind.config.js
```

## 12. Setting Up the Node.js Application

### 12.1 Initialize the Project

Create a new directory and initialize a Node.js project:

```bash
mkdir node-js-jwt-auth-postgresql
cd node-js-jwt-auth-postgresql
npm init -y
```

### 12.2 Install Dependencies

Install the necessary packages:

```bash
npm install express sequelize pg pg-hstore cors jsonwebtoken bcryptjs
```

To enable ES Module syntax, update `package.json`:

```json
{
    "type": "module"
    // ... other configurations
}
```

## 13. Setting Up the Express Web Server

Create a `server.js` file in the root directory:

```javascript
// server.js
import express from "express";
import cors from "cors";
import db from "./app/models/index.js";
import authRoutes from "./app/routes/auth.routes.js";
import userRoutes from "./app/routes/user.routes.js";

const app = express();

const corsOptions = {
    origin: "http://localhost:8081",
};

app.use(cors(corsOptions));

// Parse requests of content-type - application/json
app.use(express.json());

// Parse requests of content-type - application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));

// Simple route
app.get("/", (req, res) => {
    res.json({ message: "Welcome to the Node.js JWT Authentication application." });
});

// Routes
app.use("/api/auth", authRoutes);
app.use("/api/test", userRoutes);

// Set port, listen for requests
const PORT = process.env.PORT || 8080;

db.sequelize.sync().then(() => {
    app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}.`);
    });
});
```

**Explanation:**

- **CORS Configuration:** Allows requests from `http://localhost:8081`.
- **Middleware:** Parses JSON and URL-encoded data.
- **Routes:** Includes authentication and user-related routes.
- **Database Synchronization:** Ensures Sequelize syncs models with the PostgreSQL
  database before starting the server.

## 14. Configuring PostgreSQL Database & Sequelize

Create a configuration file for the database in `app/config/db.config.js`:

```javascript
// app/config/db.config.js
export default {
    HOST: "localhost",
    USER: "postgres",
    PASSWORD: "your_password",
    DB: "testdb",
    dialect: "postgres",
    pool: {
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000,
    },
};
```

### 14.1 Initializing Sequelize

Set up Sequelize in `app/models/index.js`:

```javascript
// app/models/index.js
import Sequelize from "sequelize";
import dbConfig from "../config/db.config.js";
import UserModel from "./user.model.js";
import RoleModel from "./role.model.js";

const sequelize = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {
    host: dbConfig.HOST,
    dialect: dbConfig.dialect,
    pool: dbConfig.pool,
});

const db = {};

db.Sequelize = Sequelize;
db.sequelize = sequelize;

db.user = UserModel(sequelize, Sequelize);
db.role = RoleModel(sequelize, Sequelize);

db.role.belongsToMany(db.user, { through: "user_roles" });
db.user.belongsToMany(db.role, { through: "user_roles", as: "roles" });

db.ROLES = ["user", "admin", "moderator"];

export default db;
```

**Explanation:**

- **Models:** Imports `User` and `Role` models.
- **Associations:** Establishes a Many-to-Many relationship between `users` and `roles`
  through the `user_roles` table.
- **Roles Array:** Defines available roles.

### 14.2 Setting up a local PostgresSQL Database

If you do not have a local PostgreSQL instance available for testing already, you can set
up a temporary local database using Docker:

```bash
docker run --rm -e POSTGRES_PASSWORD=your_password -e POSTGRES_USER=postgres -e POSTGRES_DB=testdb -p 5432:5432 postgres
```

## 15. Defining the Sequelize Models

### 15.1 User Model

Create `app/models/user.model.js`:

```javascript
// app/models/user.model.js
export default (sequelize, DataTypes) => {
    const User = sequelize.define("users", {
        username: {
            type: DataTypes.STRING,
            unique: true,
        },
        email: {
            type: DataTypes.STRING,
            unique: true,
            validate: {
                isEmail: true,
            },
        },
        password: {
            type: DataTypes.STRING,
        },
    });

    return User;
};
```

### 15.2 Role Model

Create `app/models/role.model.js`:

```javascript
// app/models/role.model.js
export default (sequelize, DataTypes) => {
    const Role = sequelize.define("roles", {
        id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true,
        },
        name: {
            type: DataTypes.STRING,
        },
    });

    return Role;
};
```

**Security Enhancement:** By making `username` and `email` unique and validating email
formats, we enhance data integrity and security.

## 16. Configuring Authentication Key

Create `app/config/auth.config.js` to store your secret key:

```javascript
// app/config/auth.config.js
export default {
    secret: "your-very-secure-secret-key",
};
```

**Security Note:** Use a strong, unpredictable secret key and store it securely,
preferably using environment variables.

## 17. Creating Middleware Functions

### 17.1 Verify Sign-Up Middleware

Create `app/middlewares/verifySignUp.js`:

```javascript
// app/middlewares/verifySignUp.js
import db from "../models/index.js";

const ROLES = db.ROLES;
const User = db.user;

const checkDuplicateUsernameOrEmail = async (req, res, next) => {
    try {
        // Check if username exists
        let user = await User.findOne({ where: { username: req.body.username } });
        if (user) {
            return res
                .status(400)
                .json({ message: "Failed! Username is already in use!" });
        }

        // Check if email exists
        user = await User.findOne({ where: { email: req.body.email } });
        if (user) {
            return res.status(400).json({ message: "Failed! Email is already in use!" });
        }

        next();
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

const checkRolesExisted = (req, res, next) => {
    if (req.body.roles) {
        for (const role of req.body.roles) {
            if (!ROLES.includes(role)) {
                return res
                    .status(400)
                    .json({ message: `Failed! Role does not exist: ${role}` });
            }
        }
    }
    next();
};

const verifySignUp = {
    checkDuplicateUsernameOrEmail,
    checkRolesExisted,
};

export default verifySignUp;
```

### 17.2 JWT Authentication Middleware

Create `app/middlewares/authJwt.js`:

```javascript
// app/middlewares/authJwt.js
import jwt from "jsonwebtoken";
import db from "../models/index.js";
import authConfig from "../config/auth.config.js";

const User = db.user;

const verifyToken = (req, res, next) => {
    const token = req.headers["x-access-token"] || req.headers["authorization"];

    if (!token) {
        return res.status(403).json({ message: "No token provided!" });
    }

    const actualToken = token.startsWith("Bearer ")
        ? token.slice(7, token.length)
        : token;

    jwt.verify(actualToken, authConfig.secret, (err, decoded) => {
        if (err) {
            return res.status(401).json({ message: "Unauthorized!" });
        }
        req.userId = decoded.id;
        next();
    });
};

const isAdmin = async (req, res, next) => {
    try {
        const user = await User.findByPk(req.userId);
        const roles = await user.getRoles();

        for (const role of roles) {
            if (role.name === "admin") {
                return next();
            }
        }

        return res.status(403).json({ message: "Require Admin Role!" });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

const isModerator = async (req, res, next) => {
    try {
        const user = await User.findByPk(req.userId);
        const roles = await user.getRoles();

        for (const role of roles) {
            if (role.name === "moderator") {
                return next();
            }
        }

        return res.status(403).json({ message: "Require Moderator Role!" });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

const isModeratorOrAdmin = async (req, res, next) => {
    try {
        const user = await User.findByPk(req.userId);
        const roles = await user.getRoles();

        for (const role of roles) {
            if (role.name === "moderator" || role.name === "admin") {
                return next();
            }
        }

        return res.status(403).json({ message: "Require Moderator or Admin Role!" });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

const authJwt = {
    verifyToken,
    isAdmin,
    isModerator,
    isModeratorOrAdmin,
};

export default authJwt;
```

**Security Enhancements:**

- **Token Verification:** Properly verifies JWTs and handles Bearer tokens.
- **Role Checks:** Asynchronously verifies user roles to prevent blocking the event loop.
- **Error Handling:** Returns appropriate HTTP status codes and error messages.

### Middleware Index

Create `app/middlewares/index.js`:

```javascript
// app/middlewares/index.js
import authJwt from "./authJwt.js";
import verifySignUp from "./verifySignUp.js";

export { authJwt, verifySignUp };
```

## 18. Creating Controllers

### 18.1 Authentication Controller

Create `app/controllers/auth.controller.js`:

```javascript
// app/controllers/auth.controller.js
import db from "../models/index.js";
import authConfig from "../config/auth.config.js";
import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";

const User = db.user;
const Role = db.role;

export const signup = async (req, res) => {
    try {
        // Create new user
        const hashedPassword = await bcrypt.hash(req.body.password, 10);
        const user = await User.create({
            username: req.body.username,
            email: req.body.email,
            password: hashedPassword,
        });

        const role = await Role.findOne({ where: { name: "user" } });
        await user.setRoles([role]);

        res.status(201).json({ message: "User registered successfully!" });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};

export const signin = async (req, res) => {
    try {
        // Find user by username
        const user = await User.findOne({
            where: {
                username: req.body.username,
            },
        });

        if (!user) {
            return res.status(404).json({ message: "User Not found." });
        }

        // Validate password
        const passwordIsValid = await bcrypt.compare(req.body.password, user.password);

        if (!passwordIsValid) {
            return res.status(401).json({
                accessToken: null,
                message: "Invalid Password!",
            });
        }

        // Generate JWT
        const token = jwt.sign({ id: user.id }, authConfig.secret, {
            expiresIn: 86400, // 24 hours
        });

        // Get user roles
        const roles = await user.getRoles();
        const authorities = roles.map((role) => `ROLE_${role.name.toUpperCase()}`);

        res.status(200).json({
            id: user.id,
            username: user.username,
            email: user.email,
            roles: authorities,
            accessToken: token,
        });
    } catch (error) {
        res.status(500).json({ message: error.message });
    }
};
```

### 18.2 User Controller

Create `app/controllers/user.controller.js`:

```javascript
// app/controllers/user.controller.js
export const allAccess = (req, res) => {
    res.status(200).send("Public Content.");
};

export const userBoard = (req, res) => {
    res.status(200).send("User Content.");
};

export const moderatorBoard = (req, res) => {
    res.status(200).send("Moderator Content.");
};

export const adminBoard = (req, res) => {
    res.status(200).send("Admin Content.");
};
```

## 19. Defining Routes

### 19.1 Authentication Routes

Create `app/routes/auth.routes.js`:

```javascript
// app/routes/auth.routes.js
import express from "express";
import { signup, signin } from "../controllers/auth.controller.js";
import { verifySignUp } from "../middlewares/index.js";

const router = express.Router();

// Signup Route
router.post(
    "/signup",
    [verifySignUp.checkDuplicateUsernameOrEmail, verifySignUp.checkRolesExisted],
    signup,
);

// Signin Route
router.post("/signin", signin);

export default router;
```

### 19.2 User Routes

Create `app/routes/user.routes.js`:

```javascript
// app/routes/user.routes.js
import express from "express";
import {
    allAccess,
    userBoard,
    moderatorBoard,
    adminBoard,
} from "../controllers/user.controller.js";
import { authJwt } from "../middlewares/index.js";

const router = express.Router();

// Public Route
router.get("/all", allAccess);

// User Route
router.get("/user", [authJwt.verifyToken], userBoard);

// Moderator Route
router.get("/mod", [authJwt.verifyToken, authJwt.isModerator], moderatorBoard);

// Admin Route
router.get("/admin", [authJwt.verifyToken, authJwt.isAdmin], adminBoard);

export default router;
```

## 20 Running the Application

### 20.1 Starting the Server

Ensure PostgreSQL is running and the database `testdb` is created. Then, start the server:

```bash
node server.js
```

You should see:

```
Server is running on port 8080.
```

### 20.2 Initializing Roles

Implement role initialization in `server.js` to populate the `roles` table:

```javascript
// server.js (add below the import statements)
const initializeRoles = async () => {
    const roles = ["user", "moderator", "admin"];
    for (const role of roles) {
        await db.role.findOrCreate({
            where: { name: role },
        });
    }
};

db.sequelize.sync().then(async () => {
    await initializeRoles();
    app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}.`);
    });
});
```

## 21. Testing the API

Use tools like **Postman** or **Insomnia** to test the API endpoints.

### 21.1 Register a New User

- **Endpoint:** `POST /api/auth/signup`
- **Body:**

    ```json
    {
        "username": "john_doe",
        "email": "john@example.com",
        "password": "securePassword123"
    }
    ```

### 21.2 Login

- **Endpoint:** `POST /api/auth/signin`

- **Body:**

    ```json
    {
        "username": "john_doe",
        "password": "securePassword123"
    }
    ```

- **Response:**

    ```json
    {
        "id": 1,
        "username": "john_doe",
        "email": "john@example.com",
        "roles": ["ROLE_ADMIN"],
        "accessToken": "your.jwt.token.here"
    }
    ```

### 21.3 Access Protected Routes

Include the `accessToken` in the `Authorization` header as a Bearer token.

- **Header:**

    ```
    Authorization: Bearer your.jwt.token.here
    ```

- **Endpoints:**
    - `GET /api/test/user` - Accessible to authenticated users.
    - `GET /api/test/mod` - Accessible to moderators.
    - `GET /api/test/admin` - Accessible to admins.

## 24 Common mistakes to avoid when implementing Node.js JWT authentication

Avoid these common pitfalls to ensure secure and effective JWT implementation:

1. **Weak secret keys:** Use a strong, unpredictable secret key for signing JWTs. Store it
   securely using environment variables.
2. **Insecure storage:** Do not store JWTs in local storage for web apps. Use HTTP-only
   cookies for better security.
3. **Ignoring expiry:** Set a reasonable expiration time for tokens and implement refresh
   tokens for longer sessions.
4. **Poor error handling:** Ensure detailed error messages are logged but do not reveal
   sensitive information to the client.

## 25. Conclusion

Congratulations! You've successfully built a secure and modern Node.js Express REST API
with JWT authentication and PostgreSQL using Sequelize. This setup provides a solid
foundation for user authentication and role-based authorization, ensuring that your
application adheres to current best practices.

**Next Steps:**

- **Implement Refresh Tokens:** Enhance security by implementing refresh tokens to allow
  users to obtain new access tokens without re-authenticating.
- **Integrate Front-End:** Develop a front-end application using frameworks like
  [React](https://www.corbado.com/blog/react-passkeys), [Angular](https://www.corbado.com/blog/angular-passkeys), or
  [Vue.js](https://www.corbado.com/blog/vuejs-passkeys) to interact with your API.
- **Deploy to Production:** Prepare your application for deployment by setting up
  environment variables, secure HTTPS connections, and scalable infrastructures.

Happy coding!
