diff --git a/README.md b/README.md index 283c589..55db26e 100644 --- a/README.md +++ b/README.md @@ -248,19 +248,8 @@ oauth2: { - **onSuccess**: Callback executed on successful OAuth2 authentication, This is where user registration/creation logic should be implemented - **onFailure**: Callback executed on OAuth2 authentication failure - **defaultRole**: Default role assigned to new users. -- **setRefreshCookie**: Set refresh token as HTTP-only cookie. -- **appendTokensInRedirect**: Include tokens in redirect URL. - **providers**: Supported providers (e.g., Google, GitHub). -``` - -- **enabled**: Enables cookie support. -- **name**: Cookie name for refresh token. -- **httpOnly**: Prevents client-side JavaScript access. -- **secure**: Only send over HTTPS. -- **sameSite**: CSRF protection setting. -- **maxAge**: Cookie expiration time. -- **path**: Cookie path. ### **Two-Factor Authentication** diff --git a/docs/nestjs_usage.md b/docs/nestjs_usage.md index 4d9c260..5bc6759 100644 --- a/docs/nestjs_usage.md +++ b/docs/nestjs_usage.md @@ -1,22 +1,23 @@ -# PassportJS Authentication Library +# NestJS Usage with Auth-Core -A unified authentication solution for **Express.js** and **NestJS** applications. This library supports **JWT**, **Session-based Authentication**, and **Google OAuth**, providing a seamless integration for both frameworks. +Auth-Core provides seamless authentication integration for **NestJS** applications, supporting **JWT**, **Session-based**, and **OAuth 2.0** authentication — all configurable through a unified API. --- ## Features -- **JWT Authentication** with Access and Refresh Tokens. -- **Session-based Authentication** for persistent user sessions. -- **Google OAuth2 Login** for social authentication. -- Middleware-based verification for secure routes. -- Easy-to-configure for both **Express.js** and **NestJS**. +- ✅ **JWT Authentication** with Access and Refresh Tokens +- ✅ **Session-based Authentication** for persistent user sessions +- ✅ **OAuth 2.0 Login** (Google, GitHub, and custom providers) +- ✅ **Two-Factor Authentication (2FA)** support +- ✅ **Role and Permission-Based Access Control** +- ✅ Plug-and-play integration with NestJS Guards --- ## Installation -Install the library via npm: +Install Auth-Core via npm: ```bash npm install @flycatch/auth-core @@ -24,19 +25,92 @@ npm install @flycatch/auth-core --- -## Usage +## Configuration in NestJS -### Configuration for NestJS +### 1. Configure Auth-Core in `main.ts` -#### Install Dependencies +Initialize Auth-Core just like in Express, but within your NestJS bootstrap function. -```bash -npm install auth-core @nestjs/auth-core +```typescript +import { NestFactory } from "@nestjs/core"; +import { AppModule } from "./app.module"; +import { config } from "@flycatch/auth-core"; +import bcrypt from "bcrypt"; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const userRepository = { + async find(email: string) { + return { + id: "123", + email, + username: "exampleUser", + grants: ["read_user"], + is2faEnabled: false, + }; + }, + }; + + app.use( + config({ + jwt: { + enabled: true, + secret: "my_jwt_secret", + expiresIn: "1h", + refresh: true, + prefix: "/auth/jwt", + }, + session: { + enabled: true, + prefix: "/auth/session", + secret: "my_session_secret", + resave: false, + saveUninitialized: true, + cookie: { secure: false, maxAge: 60000 }, + }, + oauth2: { + enabled: true, + baseURL: "http://localhost:3000", + prefix: "/auth", + successRedirect: "http://localhost:3000/oauth-success", + failureRedirect: "http://localhost:3000/oauth-failure", + providers: { + google: { + clientID: "GOOGLE_CLIENT_ID", + clientSecret: "GOOGLE_CLIENT_SECRET", + callbackURL: "/auth/google/callback", + scope: ["profile", "email"], + }, + }, + }, + userService: { + loadUser: async (email) => userRepository.find(email), + createUser: async (profile) => ({ + id: "new-user-id", + email: profile.email, + username: profile.username, + grants: ["ROLE_USER"], + }), + }, + passwordChecker: async (inputPassword, storedPassword) => + bcrypt.compare(inputPassword, storedPassword), + logs: true, + }) + ); + + await app.listen(3000); + console.log(`🚀 Server running at http://localhost:3000`); +} + +bootstrap(); ``` -#### Create an `AuthGuard` +--- + +### 2. Create a Custom `AuthGuard` -Create a custom guard in `authguard.guard.ts`: +Use Auth-Core’s `verify()` middleware inside a NestJS Guard for route protection. ```typescript import { @@ -45,7 +119,7 @@ import { ExecutionContext, UnauthorizedException, } from "@nestjs/common"; -import authCore from "@flycatch/auth-core"; // Import the library +import { verify } from "@flycatch/auth-core"; @Injectable() export class AuthGuard implements CanActivate { @@ -60,11 +134,11 @@ export class AuthGuard implements CanActivate { new UnauthorizedException(err.message || "Unauthorized") ); } - resolve(true); // Authentication passed + resolve(true); }; try { - const verifyMiddleware = authCore.verify(); + const verifyMiddleware = verify(); verifyMiddleware(req, res, next); } catch (error: any) { reject(new UnauthorizedException("Authentication error")); @@ -74,9 +148,13 @@ export class AuthGuard implements CanActivate { } ``` -#### Controller Example +> ✅ You can also pass permissions to `verify()` — e.g. `verify("admin_access")` — for role-based control. -Define a controller to protect routes in `app.controller.ts`: +--- + +### 3. Protect Routes in Your Controller + +Use the custom guard to protect any route. ```typescript import { Controller, Get, UseGuards, Req } from "@nestjs/common"; @@ -87,98 +165,96 @@ export class UserController { @Get("/") @UseGuards(AuthGuard) getUser(@Req() req) { - return { user: req.user }; + return { message: "Access granted", user: req.user }; } } ``` -#### Integrating Authentication Configuration in `main.ts` +--- -### Set up authentication in your NestJS entry point: +### 4. Example with Role-Based Access + +If your app uses permissions or roles (e.g., `"admin"`), extend your guard: ```typescript -import { NestFactory } from "@nestjs/core"; -import { AppModule } from "./app.module"; -import auth from "auth-core"; // Import the library +@Injectable() +export class RoleGuard implements CanActivate { + constructor(private requiredRole: string) {} -async function bootstrap() { - const app = await NestFactory.create(AppModule); + async canActivate(context: ExecutionContext): Promise { + const req = context.switchToHttp().getRequest(); + const res = context.switchToHttp().getResponse(); - auth.config({ - jwt: { - enabled: true, - refresh: true, - config: { secret: "your_jwt_secret" }, - }, - session: { - enabled: true, - secret: "your_session_secret", - }, - google: { - enabled: true, - clientID: "your_google_client_id", - clientSecret: "your_google_client_secret", - callbackURL: "http://localhost:3000/auth/google/callback", - }, - }); + return new Promise((resolve, reject) => { + const next = (err?: any) => { + if (err) return reject(new UnauthorizedException(err.message)); + if (!req.user?.grants?.includes(this.requiredRole)) { + return reject(new UnauthorizedException("Access denied")); + } + resolve(true); + }; - app.use(auth.initialize()); // Initialize authentication middleware - await app.listen(3000); + verify()(req, res, next); + }); + } } - -bootstrap(); ``` ---- - -## Authentication Methods Supported - -1. **JWT Authentication** +Usage: - - Provides token-based authentication with access and refresh tokens. - - Tokens are validated using the `auth.verify()` middleware. +```typescript +@Controller("admin") +export class AdminController { + @Get("/") + @UseGuards(new RoleGuard("admin")) + getAdminDashboard(@Req() req) { + return { message: "Admin Access", user: req.user }; + } +} +``` -2. **Session-based Authentication** +--- - - Securely stores user data in the session. - - Sessions are validated using the `auth.verify()` middleware. +## Supported Authentication Methods -3. **Google OAuth** - - Enables login via Google. - - The library handles redirection, callback, and verification seamlessly. +| Method | Description | +| ------------- | --------------------------------------------------------------- | +| **JWT** | Token-based authentication with access & refresh tokens | +| **Session** | Persistent user sessions stored securely | +| **OAuth 2.0** | Social login via Google, GitHub, or custom providers | +| **2FA** | Optional OTP-based two-factor verification for sensitive logins | --- ## Testing Authentication -### Protected Route +### JWT-Protected Route -Once configured, access a protected route: +```bash +curl -X GET http://localhost:3000/user \ + -H "Authorization: Bearer " +``` -#### NestJS: +### OAuth 2.0 Login ```bash -curl -X GET http://localhost:3000/user -H "Authorization: Bearer " +http://localhost:3000/auth/google ``` -### Google OAuth Login - -1. Navigate to: `http://localhost:3000/auth/google` -2. Authenticate via Google. -3. Access your user details from `/user`. +After successful login, user data is returned from `/user`. --- ## Contributing -Contributions are welcome! Fork the repository and create a PR with your changes. +Contributions are welcome! Please fork the repository and submit a pull request. --- ## License -GPL-3.0 License. See the `LICENSE` file for details. +This project is licensed under the **GPL-3.0 License**. --- -## [Back](../README.md) +## [⬅ Back to Main README](../README.md) diff --git a/package.json b/package.json index 1c3c844..0fd7fb9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flycatch/auth-core", - "version": "1.2.0", + "version": "1.3.0", "description": "A unified authentication module for Express.js, NestJS frameworks, supporting JWT, session-based, and Google OAuth login.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/interfaces/config.interface.ts b/src/interfaces/config.interface.ts index ae80ad7..93813b1 100644 --- a/src/interfaces/config.interface.ts +++ b/src/interfaces/config.interface.ts @@ -125,8 +125,6 @@ export interface OAuth2Config { successRedirect: string; // Redirect URL on successful login failureRedirect: string; // Redirect URL on failed login defaultRole?: string; // Default role assigned to new users - setRefreshCookie?: boolean; // Store refresh token as an HTTP cookie - appendTokensInRedirect?: boolean; // Append tokens in redirect URL /** * Callback executed on successful OAuth2 authentication diff --git a/src/routes/oauth2.routes.ts b/src/routes/oauth2.routes.ts index 96a1943..400bd57 100644 --- a/src/routes/oauth2.routes.ts +++ b/src/routes/oauth2.routes.ts @@ -158,27 +158,43 @@ export default (router: Router, config: Config) => { user.provider = callbackInfo.provider; user.providerId = callbackInfo.profile.providerId; - // Initialize auth result variables - let authResult; - let accessToken: string | undefined; - let refreshToken: string | undefined; - /** * JWT Authentication */ if (config.jwt?.enabled) { - authResult = createJwtTokens(config.jwt, user); - accessToken = authResult.accessToken; - refreshToken = authResult.refreshToken; + const { refreshToken, accessToken } = createJwtTokens( + config.jwt, + user + ); logger.info("JWT tokens created for OAuth user"); + if (config.jwt?.refresh && refreshToken) { + res.cookie("AuthRefreshToken", refreshToken, { + httpOnly: false, + secure: true, + sameSite: "strict", + maxAge: 5 * 60 * 1000, + path: "/", + }); + logger.info("Refresh token set as cookie"); + } else { + res.cookie("AuthToken", accessToken, { + httpOnly: false, + secure: true, + sameSite: "strict", + maxAge: 5 * 60 * 1000, + path: "/", + }); + logger.info("Access token set as cookie"); + } + /** * Session-based Authentication */ } else if (config.session?.enabled) { - authResult = createSessionPayload(user); - req.session.user = authResult; + const sessionPayload = createSessionPayload(user); + req.session.user = sessionPayload; logger.info("Session created for OAuth user"); } else { logger.error("No authentication method configured"); @@ -191,29 +207,8 @@ export default (router: Router, config: Config) => { ); } - /** - * Set refresh token as HTTP-only cookie if configured - */ - if (config.oauth2?.setRefreshCookie && refreshToken) { - res.cookie("AuthRefreshToken", refreshToken, { - httpOnly: true, - secure: true, - sameSite: "strict", - maxAge: 5 * 60 * 1000, - path: "/", - }); - logger.info("Refresh token set as HTTP-only cookie"); - } - // Redirect to success URL - return handleOAuthSuccess( - res, - config, - providerName, - accessToken, - refreshToken, - user - ); + return handleOAuthSuccess(res, config, providerName, user); } catch (err: any) { logger.error(`Error during ${providerName} OAuth callback`, { error: err.message, @@ -273,24 +268,13 @@ const handleOAuthSuccess = ( res: Response, config: Config, providerName: string, - accessToken?: string, - refreshToken?: string, user?: User ) => { const successUrl = new URL(config.oauth2!.successRedirect); successUrl.searchParams.set("provider", providerName); - if (config.oauth2!.appendTokensInRedirect) { - if (accessToken) { - successUrl.searchParams.set("accessToken", accessToken); - } - if (refreshToken) { - successUrl.searchParams.set("refreshToken", refreshToken); - } - } - - if (user && !config.oauth2!.appendTokensInRedirect) { + if (user) { successUrl.searchParams.set("user", JSON.stringify(user)); }