Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
510 changes: 510 additions & 0 deletions FRONTEGG_POC.md

Large diffs are not rendered by default.

186 changes: 186 additions & 0 deletions FRONTEGG_QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Frontegg POC Quick Start

This guide helps you quickly test the Frontegg POC implementation.

## Prerequisites

1. Frontegg account (sign up at https://portal.frontegg.com/)
2. A Frontegg workspace configured with your application

## Setup

### 1. Get Frontegg Credentials

1. Log into [Frontegg Portal](https://portal.frontegg.com/)
2. Navigate to **Settings > General > API Keys**
3. Copy your:
- Client ID
- API Key
4. Note your Base URL (e.g., `https://app-xxxx.frontegg.com`)

### 2. Configure Backend

Create or update `packages/backend/.env`:

```bash
# ... existing env vars ...

# Frontegg POC Configuration
FRONTEGG_CLIENT_ID=your-client-id-here
FRONTEGG_API_KEY=your-api-key-here
FRONTEGG_BASE_URL=https://app-xxxx.frontegg.com
```

### 3. Configure Frontend (Optional)

To enable the Frontegg React provider, set environment variables for webpack:

```bash
export FRONTEGG_CLIENT_ID=your-client-id-here
export FRONTEGG_BASE_URL=https://app-xxxx.frontegg.com
```

Or add to your webpack environment configuration.

## Testing

### Backend Testing

1. **Start the backend**:

```bash
yarn dev:backend
```

2. **Test health endpoint**:

```bash
curl http://localhost:3000/api/frontegg/health
```

Expected response:

```json
{
"service": "frontegg",
"status": "configured",
"message": "Frontegg is configured and ready"
}
```

3. **Test with a Frontegg JWT token** (get from Frontegg dashboard or login UI):

```bash
TOKEN="your-frontegg-jwt-token"

# Verify session
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/frontegg/auth/verify

# Get user info
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/frontegg/auth/me
```

### Frontend Integration (Optional)

To add Frontegg provider to the web app, wrap your App component:

```tsx
// In packages/web/src/App.tsx
import { FronteggProvider } from "@web/auth/FronteggProvider";

// Wrap your app
<FronteggProvider>{/* Your existing app content */}</FronteggProvider>;
```

Then start the web app:

```bash
yarn dev:web
```

## API Endpoints

The POC adds these new endpoints:

- `GET /api/frontegg/health` - Check if Frontegg is configured
- `GET /api/frontegg/auth/verify` - Verify JWT token (requires auth header)
- `GET /api/frontegg/auth/me` - Get current user (requires auth header)
- `POST /api/frontegg/auth/session/revoke` - Revoke sessions (dev only, requires auth header)

## Troubleshooting

### "Frontegg not configured" message

**Cause**: Environment variables not set or backend not restarted.

**Solution**:

1. Verify `.env` file has correct values
2. Restart backend: `yarn dev:backend`

### API returns 401 Unauthorized

**Cause**: Invalid or expired JWT token.

**Solution**:

1. Get a fresh token from Frontegg portal or login UI
2. Verify token format: `Bearer your-jwt-token-here`

### Health endpoint returns "not configured"

**Cause**: Environment variables missing.

**Solution**:

1. Check `FRONTEGG_CLIENT_ID` and `FRONTEGG_API_KEY` in `.env`
2. Restart backend

## Session Testing

### Test Idle Timeout (recommended for POC validation)

1. **Configure timeout in Frontegg dashboard**:
- Go to Security > Session Management
- Set idle timeout to 5 minutes (for testing)
- Save configuration

2. **Test the flow**:
- Login through Frontegg
- Wait 6 minutes without activity
- Try to access a protected endpoint
- Should receive 401 Unauthorized

3. **Test active session**:
- Login through Frontegg
- Make API requests every 2-3 minutes
- Session should remain valid beyond idle timeout

### Test Session Revocation

```bash
# With valid token
TOKEN="your-token"

# Revoke sessions
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/frontegg/auth/session/revoke

# Try to use token again - should fail
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:3000/api/frontegg/auth/verify
```

## Notes

- This is a **POC implementation** for evaluation purposes
- SuperTokens remains the primary authentication system
- All SuperTokens functionality is preserved and unchanged
- Frontegg runs in parallel and can be disabled by removing env vars

## Documentation

See [FRONTEGG_POC.md](./FRONTEGG_POC.md) for complete documentation.
21 changes: 20 additions & 1 deletion packages/backend/.env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,23 @@ SUPERTOKENS_KEY=UNIQUE_KEY_FROM_YOUR_SUPERTOKENS_ACCOUNT
# 8. Posthog (optional)
####################################################
# POSTHOG_KEY=YOUR_POSTHOG_KEY
# POSTHOG_HOST=YOUR_POSTHOG_HOST
# POSTHOG_HOST=YOUR_POSTHOG_HOST

####################################################
# 9. Frontegg POC (optional)
####################################################
# Frontegg authentication POC configuration
# Get these from your Frontegg workspace
# https://portal.frontegg.com/

# FRONTEGG_CLIENT_ID will look something like:
# a1b2c3d4-e5f6-7890-abcd-ef1234567890
# FRONTEGG_CLIENT_ID=YOUR_FRONTEGG_CLIENT_ID

# FRONTEGG_API_KEY will look something like:
# fe_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
# FRONTEGG_API_KEY=YOUR_FRONTEGG_API_KEY

# FRONTEGG_BASE_URL is your Frontegg environment URL:
# https://app-xxxxxxxxxxxxxxxx.frontegg.com
# FRONTEGG_BASE_URL=YOUR_FRONTEGG_BASE_URL
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"main": "build/src/app.js",
"dependencies": {
"@compass/core": "1.0.0",
"@frontegg/client": "^5.3.2",
"@frontegg/rest-api": "^7.92.0",
"chalk": "4.1.2",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
Expand Down
106 changes: 106 additions & 0 deletions packages/backend/src/auth/controllers/frontegg.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Request, Response } from "express";
import { Logger } from "@core/logger/winston.logger";
import fronteggAuthService from "@backend/auth/services/frontegg.auth.service";
import { Res_Promise } from "@backend/common/types/express.types";

const logger = Logger("app:frontegg.controller");

class FronteggController {
/**
* Verify that a Frontegg session is valid
* This endpoint is protected by verifyFronteggSession middleware
*/
verifySession = (req: Request, res: Res_Promise) => {
const user = req.fronteggUser;

if (!user) {
res.promise({
error: "No user information available",
isValid: false,
});
return;
}

res.promise({
isValid: true,
user: {
id: user.id,
email: user.email,
name: user.name,
tenantId: user.tenantId,
roles: user.roles,
},
message: "Session is valid",
});
};

/**
* Get user information from the Frontegg token
*/
getUserInfo = (req: Request, res: Res_Promise) => {
const user = req.fronteggUser;

if (!user) {
res.promise({
error: "No user information available",
});
return;
}

res.promise({
user: {
id: user.id,
email: user.email,
name: user.name,
tenantId: user.tenantId,
tenantIds: user.tenantIds,
roles: user.roles,
permissions: user.permissions,
},
});
};

/**
* Revoke all sessions for a user
* Dev endpoint for testing session revocation
*/
revokeUserSessions = async (req: Request, res: Response) => {
try {
const user = req.fronteggUser;

if (!user) {
res.status(400).send({ error: "No user information available" });
return;
}

const result = await fronteggAuthService.revokeUserSessions(user.id);

res.send({
message: `Revoked sessions for user ${user.email}`,
sessionsRevoked: result.revoked,
});
} catch (error) {
logger.error("Failed to revoke user sessions:", error);
res.status(500).send({
error: "Failed to revoke user sessions",
});
}
};

/**
* Health check endpoint to verify Frontegg is configured
*/
healthCheck = (_req: Request, res: Response) => {
const isConfigured = fronteggAuthService.isConfigured();

res.send({
service: "frontegg",
status: isConfigured ? "configured" : "not configured",
message: isConfigured
? "Frontegg is configured and ready"
: "Frontegg environment variables not set",
});
};
}

export default new FronteggController();
48 changes: 48 additions & 0 deletions packages/backend/src/auth/frontegg.routes.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import express from "express";
import fronteggController from "@backend/auth/controllers/frontegg.controller";
import authMiddleware from "@backend/auth/middleware/auth.middleware";
import { verifyFronteggSession } from "@backend/auth/middleware/frontegg.auth.middleware";
import { CommonRoutesConfig } from "@backend/common/common.routes.config";

/**
* Frontegg POC routes
* These routes demonstrate Frontegg authentication capabilities
* parallel to the existing SuperTokens implementation
*/
export class FronteggAuthRoutes extends CommonRoutesConfig {
constructor(app: express.Application) {
super(app, "FronteggAuthRoutes");
}

configureRoutes(): express.Application {
/**
* Test endpoint to verify Frontegg session
* Requires a valid Frontegg JWT token in Authorization header
*/
this.app
.route(`/api/frontegg/auth/verify`)
.get([verifyFronteggSession, fronteggController.verifySession]);

/**
* Get user information from Frontegg token
*/
this.app
.route(`/api/frontegg/auth/me`)
.get([verifyFronteggSession, fronteggController.getUserInfo]);

/**
* Revoke all sessions for a user (dev only)
*/
this.app
.route(`/api/frontegg/auth/session/revoke`)
.all(authMiddleware.verifyIsDev)
.post([verifyFronteggSession, fronteggController.revokeUserSessions]);

/**
* Health check for Frontegg configuration
*/
this.app.route(`/api/frontegg/health`).get(fronteggController.healthCheck);

return this.app;
}
}
Loading