-
Notifications
You must be signed in to change notification settings - Fork 1
[137] Custom Backend JWT Issuer #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
7f12ad3
54ff0a1
3002cf3
ded2050
07c82eb
363338a
3a27aa8
11b5c26
aae0e62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Custom Issuer Service Environment Variables | ||
| # Copy this file to .env and update with your actual values | ||
|
|
||
| # ============================================================================= | ||
| # REQUIRED - Application will not start without these | ||
| # ============================================================================= | ||
|
|
||
| # Base64-encoded RSA private key used for signing issued tokens | ||
| # To encode a key file: cat signing-key.pem | base64 | ||
| # Example: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVB... | ||
| KEY_BASE64= | ||
|
|
||
| # Firebase URL where the validation public key can be retrieved | ||
| # Firebase certificate format: https://www.googleapis.com/robot/v1/metadata/x509/{service-account-email} | ||
| # Example: https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk@project.iam.gserviceaccount.com | ||
| VALIDATION_PUBLIC_KEY_URL= | ||
|
|
||
| # Expected issuer URL that incoming JWTs must have in their 'iss' claim | ||
| # This should match the issuer URL of the tokens you want to accept | ||
| # Example: https://securetoken.google.com/{project-id} | ||
| VALIDATION_ISSUER_URL= | ||
|
|
||
| # URL of the issuer service (will be included as 'iss' claim in issued tokens) | ||
| # Example: http://localhost:3000 | ||
| ISSUER_URL= | ||
|
|
||
| # ============================================================================= | ||
| # OPTIONAL - Application will use defaults if not set | ||
| # ============================================================================= | ||
|
|
||
| # Port number for the HTTP server (default: 3000) | ||
| PORT=3000 | ||
|
|
||
| # Comma-separated list of allowed CORS origins | ||
| # If not set, CORS will be disabled (no cross-origin requests allowed) | ||
| # Example: http://localhost:3000,https://example.com,https://app.example.com | ||
| ALLOWED_ORIGINS= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| # compiled output | ||
| /dist | ||
| /node_modules | ||
| /build | ||
|
|
||
| # Logs | ||
| logs | ||
| *.log | ||
| npm-debug.log* | ||
| pnpm-debug.log* | ||
| yarn-debug.log* | ||
| yarn-error.log* | ||
| lerna-debug.log* | ||
|
|
||
| # OS | ||
| .DS_Store | ||
|
|
||
| # Tests | ||
| /coverage | ||
| /.nyc_output | ||
|
|
||
| # IDEs and editors | ||
| /.idea | ||
| .project | ||
| .classpath | ||
| .c9/ | ||
| *.launch | ||
| .settings/ | ||
| *.sublime-workspace | ||
|
|
||
| # IDE - VSCode | ||
| .vscode/* | ||
| !.vscode/settings.json | ||
| !.vscode/tasks.json | ||
| !.vscode/launch.json | ||
| !.vscode/extensions.json | ||
|
|
||
| # dotenv environment variable files | ||
| .env | ||
| .env.development.local | ||
| .env.test.local | ||
| .env.production.local | ||
| .env.local | ||
|
|
||
| # temp directory | ||
| .temp | ||
| .tmp | ||
|
|
||
| # Runtime data | ||
| pids | ||
| *.pid | ||
| *.seed | ||
| *.pid.lock | ||
|
|
||
| # Diagnostic reports (https://nodejs.org/api/report.html) | ||
| report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||
|
|
||
| # Keys | ||
| keys/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "singleQuote": true, | ||
| "trailingComma": "all" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| # Custom Issuer Service | ||
|
|
||
| A NestJS service that validates incoming JWTs and issues new tokens with a different signing key. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Install Dependencies | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| ### 2. Generate Test Keys (Optional) | ||
|
|
||
| ```bash | ||
| # Generate RSA key pair for testing | ||
| mkdir -p keys | ||
| openssl genrsa -out keys/signing-key.pem 2048 | ||
| openssl rsa -in keys/signing-key.pem -pubout -out keys/validation-public-key.pem | ||
| chmod 600 keys/signing-key.pem | ||
| chmod 644 keys/validation-public-key.pem | ||
| ``` | ||
|
|
||
| ### 3. Configure Environment Variables | ||
|
|
||
| Copy `.env.example` to `.env` and fill in the required values: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| Required variables: | ||
| - `KEY_BASE64` - Base64-encoded RSA private key for signing tokens (encode with: `cat signing-key.pem | base64`) | ||
| - `VALIDATION_PUBLIC_KEY_URL` - Firebase URL where the validation public key can be retrieved (e.g., `https://www.googleapis.com/robot/v1/metadata/x509/{service-account-email}`) | ||
| - `VALIDATION_ISSUER_URL` - Expected issuer URL that incoming JWTs must have in their `iss` claim (e.g., `https://securetoken.google.com/{project-id}`) | ||
| - `ISSUER_URL` - URL of the issuer service (e.g., `http://localhost:3000`) | ||
|
|
||
| Optional variables: | ||
| - `PORT` - Server port (default: 3000) | ||
| - `ALLOWED_ORIGINS` - Comma-separated CORS origins | ||
|
|
||
| ### 4. Start the Service | ||
|
|
||
| ```bash | ||
| # Development mode (with hot reload) | ||
| pnpm start:dev | ||
|
|
||
| # Production mode | ||
| pnpm build | ||
| pnpm start:prod | ||
| ``` | ||
|
|
||
| The service will be available at `http://localhost:3000` (or your configured port). | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Issue a Token | ||
|
|
||
| ```bash | ||
| POST /issuer/issue | ||
| Content-Type: application/json | ||
|
|
||
| { | ||
| "jwt": "your-validated-jwt-token" | ||
| } | ||
| ``` | ||
|
|
||
| The service will: | ||
| 1. Validate the input JWT using the validation public key | ||
| 2. Extract `sub`, `exp`, and `nbf` claims | ||
| 3. Issue a new token signed with the signing private key | ||
| 4. Include the `iss` claim with the issuer URL | ||
|
|
||
| ### Generate Test JWTs | ||
|
|
||
| ```bash | ||
| # Generate and send a test JWT to the service | ||
| pnpm generate:jwt | ||
|
|
||
| # Generate with custom options | ||
| pnpm generate:jwt -- --sub "user@example.com" --exp 7200 | ||
| ``` | ||
|
|
||
| ## Scripts | ||
|
|
||
| - `pnpm start:dev` - Start in development mode with hot reload | ||
| - `pnpm build` - Build for production | ||
| - `pnpm start:prod` - Start production server | ||
| - `pnpm test` - Run tests | ||
| - `pnpm generate:jwt` - Generate test JWT tokens | ||
|
|
||
| ## Security | ||
|
|
||
| - Input validation with `class-validator` | ||
| - Request size limits (10KB) | ||
| - CORS protection | ||
| - Path traversal protection for key files | ||
| - Sensitive data redaction in error logs | ||
| - Rate limiting recommended for production |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| // @ts-check | ||
| import eslint from '@eslint/js'; | ||
| import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; | ||
| import globals from 'globals'; | ||
| import tseslint from 'typescript-eslint'; | ||
|
|
||
| export default tseslint.config( | ||
| { | ||
| ignores: ['eslint.config.mjs'], | ||
| }, | ||
| eslint.configs.recommended, | ||
| ...tseslint.configs.recommendedTypeChecked, | ||
| eslintPluginPrettierRecommended, | ||
| { | ||
| languageOptions: { | ||
| globals: { | ||
| ...globals.node, | ||
| ...globals.jest, | ||
| }, | ||
| sourceType: 'commonjs', | ||
| parserOptions: { | ||
| projectService: true, | ||
| tsconfigRootDir: import.meta.dirname, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| rules: { | ||
| '@typescript-eslint/no-explicit-any': 'off', | ||
| '@typescript-eslint/no-floating-promises': 'warn', | ||
| '@typescript-eslint/no-unsafe-argument': 'warn', | ||
| "prettier/prettier": ["error", { endOfLine: "auto" }], | ||
| }, | ||
| }, | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "$schema": "https://json.schemastore.org/nest-cli", | ||
| "collection": "@nestjs/schematics", | ||
| "sourceRoot": "src", | ||
| "compilerOptions": { | ||
| "deleteOutDir": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| { | ||
| "name": "custom-issuer", | ||
| "version": "0.0.1", | ||
| "description": "", | ||
| "author": "", | ||
| "private": true, | ||
| "license": "UNLICENSED", | ||
| "scripts": { | ||
| "build": "nest build", | ||
| "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", | ||
| "start": "nest start", | ||
| "start:dev": "nest start --watch", | ||
| "start:debug": "nest start --debug --watch", | ||
| "start:prod": "node dist/main", | ||
| "generate:jwt": "node scripts/generate-test-jwt.js" | ||
| }, | ||
| "dependencies": { | ||
| "@nestjs/common": "^11.0.1", | ||
| "@nestjs/config": "^3.1.1", | ||
| "@nestjs/core": "^11.0.1", | ||
| "@nestjs/platform-express": "^11.0.1", | ||
| "class-transformer": "0.5.1", | ||
| "class-validator": "0.14.0", | ||
| "reflect-metadata": "^0.2.2", | ||
| "rxjs": "^7.8.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@eslint/eslintrc": "^3.2.0", | ||
| "@eslint/js": "^9.18.0", | ||
| "@nestjs/cli": "^11.0.0", | ||
| "@nestjs/schematics": "^11.0.0", | ||
| "@nestjs/testing": "^11.0.1", | ||
| "@types/express": "^5.0.0", | ||
| "@types/jest": "^30.0.0", | ||
| "@types/jsonwebtoken": "^9.0.5", | ||
| "@types/node": "^22.10.7", | ||
| "@types/supertest": "^6.0.2", | ||
| "eslint": "^9.18.0", | ||
| "eslint-config-prettier": "^10.0.1", | ||
| "eslint-plugin-prettier": "^5.2.2", | ||
| "globals": "^16.0.0", | ||
| "jest": "^30.0.0", | ||
| "jsonwebtoken": "^9.0.2", | ||
|
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: rg -n "require\(['\"]jsonwebtoken['\"]|from ['\"]jsonwebtoken['\"]" apps/custom-issuer/src/Repository: Peersyst/fast-auth Length of output: 156 Move
🤖 Prompt for AI Agents |
||
| "prettier": "^3.4.2", | ||
| "source-map-support": "^0.5.21", | ||
| "supertest": "^7.0.0", | ||
| "ts-jest": "^29.2.5", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat apps/custom-issuer/package.json | grep -E '"jest":|"ts-jest":'Repository: Peersyst/fast-auth Length of output: 122 🌐 Web query:
💡 Result: Yes — ts-jest added Jest 30 support in the 29.x line. Upgrade to ts-jest >= 29.4.0 to use it with Jest 30. [1][2] Sources: Upgrade ts-jest to 29.4.0 or higher for Jest 30 compatibility. ts-jest 29.2.5 does not support Jest 30. Jest 30 support was added in ts-jest 29.4.0. Upgrade to 🤖 Prompt for AI Agents |
||
| "ts-loader": "^9.5.2", | ||
| "ts-node": "^10.9.2", | ||
| "tsconfig-paths": "^4.2.0", | ||
| "typescript": "^5.7.3", | ||
| "typescript-eslint": "^8.20.0" | ||
| }, | ||
| "jest": { | ||
| "preset": "ts-jest", | ||
| "moduleFileExtensions": [ | ||
| "js", | ||
| "json", | ||
| "ts" | ||
| ], | ||
| "roots": [ | ||
| "<rootDir>/src", | ||
| "<rootDir>/test" | ||
| ], | ||
| "testRegex": ".*\\.spec\\.ts$", | ||
| "transform": { | ||
| "^.+\\.(t|j)s$": [ | ||
| "ts-jest", | ||
| { | ||
| "tsconfig": { | ||
| "module": "commonjs" | ||
| } | ||
| } | ||
| ] | ||
| }, | ||
| "collectCoverageFrom": [ | ||
| "src/**/*.(t|j)s" | ||
| ], | ||
| "coverageDirectory": "../coverage", | ||
| "testEnvironment": "node", | ||
| "moduleNameMapper": { | ||
| "^src/(.*)$": "<rootDir>/src/$1" | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: Peersyst/fast-auth
Length of output: 82
🏁 Script executed:
Repository: Peersyst/fast-auth
Length of output: 1439
Change
sourceTypefrom'commonjs'to'module'.The codebase extensively uses ES module syntax (
import/exportstatements) throughout the source files, but ESLint'ssourceTypeis configured as'commonjs'. This mismatch causes the parser to analyze code with incorrect syntax expectations, which can lead to missed linting errors or incorrect rule behavior. Update tosourceType: 'module'to align with the project's actual module system.🤖 Prompt for AI Agents