{"id":28891678,"url":"https://github.com/zitadel/example-auth-expressjs","last_synced_at":"2026-05-08T04:34:39.030Z","repository":{"id":299398237,"uuid":"1002832342","full_name":"zitadel/example-auth-expressjs","owner":"zitadel","description":"A guide to securing Express apps with ZITADEL using AuthJS, OIDC, and the PKCE flow.","archived":false,"fork":false,"pushed_at":"2026-04-28T07:35:50.000Z","size":291,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-28T09:28:36.467Z","etag":null,"topics":["bff","example","expressjs","iam","node","oauth2","oidc","openid","passportjs","pkce","session","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zitadel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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-06-16T08:03:03.000Z","updated_at":"2026-04-28T07:35:54.000Z","dependencies_parsed_at":"2025-06-16T11:27:28.600Z","dependency_job_id":"fee7956d-9bf0-49cb-b545-a52b7bfc8426","html_url":"https://github.com/zitadel/example-auth-expressjs","commit_stats":null,"previous_names":["zitadel/example-auth-expressjs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/zitadel/example-auth-expressjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zitadel%2Fexample-auth-expressjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zitadel%2Fexample-auth-expressjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zitadel%2Fexample-auth-expressjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zitadel%2Fexample-auth-expressjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zitadel","download_url":"https://codeload.github.com/zitadel/example-auth-expressjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zitadel%2Fexample-auth-expressjs/sbom","scorecard":{"id":1239974,"data":{"date":"2025-11-23T18:43:28Z","repo":{"name":"github.com/zitadel/example-auth-expressjs","commit":"d6b4e74a211074c40087e48febb4948034193608"},"scorecard":{"version":"v5.1.1","commit":"cd152cb6742c5b8f2f3d2b5193b41d9c50905198"},"score":5,"checks":[{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#dependency-update-tool"}},{"name":"Maintained","score":7,"reason":"9 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 7","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/23 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/commitlint.yml:16","Info: jobLevel 'pull-requests' permission set to 'read': .github/workflows/commitlint.yml:17","Warn: jobLevel 'contents' permission set to 'write': .github/workflows/linting.yml:24","Info: jobLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:16","Info: jobLevel 'contents' permission set to 'read': .github/workflows/unused.yml:16","Info: jobLevel 'pull-requests' permission set to 'read': .github/workflows/unused.yml:17","Info: topLevel 'contents' permission set to 'read': .github/workflows/commitlint.yml:11","Info: topLevel 'contents' permission set to 'read': .github/workflows/depcheck.yml:7","Info: topLevel 'contents' permission set to 'read': .github/workflows/linting.yml:19","Warn: topLevel 'contents' permission set to 'write': .github/workflows/pipeline.yml:7","Info: topLevel 'actions' permission set to 'read': .github/workflows/pipeline.yml:8","Warn: topLevel 'checks' permission set to 'write': .github/workflows/pipeline.yml:9","Info: topLevel 'contents' permission set to 'read': .github/workflows/scorecard.yml:9","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: topLevel 'contents' permission set to 'read': .github/workflows/typecheck.yml:15","Info: topLevel 'contents' permission set to 'read': .github/workflows/unused.yml:11"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":8,"reason":"dependency not pinned by hash detected -- score normalized to 8","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/commitlint.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/commitlint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linting.yml:40: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/linting.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/typecheck.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/typecheck.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/unused.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/unused.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/unused.yml:42: update your workflow using https://app.stepsecurity.io/secureworkflow/zitadel/example-auth-expressjs/unused.yml/main?enable=pin","Info:  10 out of  14 GitHub-owned GitHubAction dependencies pinned","Info:  11 out of  14 third-party GitHubAction dependencies pinned","Info:   4 out of   4 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#cii-best-practices"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-xffm-g5w8-qvg7"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#security-policy"}},{"name":"Contributors","score":0,"reason":"project has 0 contributing companies or organizations -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#contributors"}},{"name":"CI-Tests","score":10,"reason":"3 out of 3 merged PRs checked by a CI test -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/cd152cb6742c5b8f2f3d2b5193b41d9c50905198/docs/checks.md#ci-tests"}}]},"last_synced_at":"2025-11-23T20:24:53.233Z","repository_id":299398237,"created_at":"2025-11-23T20:24:53.233Z","updated_at":"2025-11-23T20:24:53.233Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32767188,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T02:36:36.067Z","status":"ssl_error","status_checked_at":"2026-05-08T02:36:07.210Z","response_time":54,"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":["bff","example","expressjs","iam","node","oauth2","oidc","openid","passportjs","pkce","session","typescript"],"created_at":"2025-06-21T01:05:07.820Z","updated_at":"2026-05-08T04:34:39.025Z","avatar_url":"https://github.com/zitadel.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Express with ZITADEL\n\n[Express](https://expressjs.com/) is a fast, unopinionated, minimalist web framework for Node.js. It provides a robust set of features for web and mobile applications, making it one of the most popular choices for building server-side applications. In a modern setup, your Express application manages both your application's frontend and backend logic through server-side routes and middleware.\n\nTo secure such an application, you need a reliable way to handle user logins. For the Express ecosystem, [Auth.js](https://authjs.dev/) (formerly NextAuth.js) is the standard and recommended library for authentication. Think of it as a flexible security guard for your app. This guide demonstrates how to use Auth.js with an Express application to implement a secure login with ZITADEL.\n\nWe'll be using the **OpenID Connect (OIDC)** protocol with the **Authorization Code Flow + PKCE**. This is the industry-best practice for security, ensuring that the login process is safe from start to finish. You can learn more in our [guide to OAuth 2.0 recommended flows](https://zitadel.com/docs/guides/integrate/login/oidc/oauth-recommended-flows).\n\nThis example uses **Auth.js**, the standard for Express authentication. While ZITADEL doesn't offer a specific SDK, Auth.js is highly modular. It works with a \"provider\" that handles the communication with ZITADEL. Under the hood, this example uses the powerful OIDC standard to manage the secure PKCE flow.\n\nCheck out our Example Application to see it in action.\n\n## Example Application\n\nThe example repository includes a complete Express application, ready to run, that demonstrates how to integrate ZITADEL for user authentication.\n\nThis example application showcases a typical web app authentication pattern: users start on a public landing page, click a login button to authenticate with ZITADEL, and are then redirected to a protected profile page displaying their user information. The app also includes secure logout functionality that clears the session and redirects users back to ZITADEL's logout endpoint. All protected routes are automatically secured using Auth.js middleware and session management, ensuring only authenticated users can access sensitive areas of your application.\n\n### Prerequisites\n\nBefore you begin, ensure you have the following:\n\n#### System Requirements\n\n- Node.js (v20 or later is recommended)\n- npm, yarn, or pnpm package manager\n\n#### Account Setup\n\nYou'll need a ZITADEL account and application configured. Follow the [ZITADEL documentation on creating applications](https://zitadel.com/docs/guides/integrate/login/oidc/web-app) to set up your account and create a Web application with Authorization Code + PKCE flow.\n\n\u003e **Important:** Configure the following URLs in your ZITADEL application settings:\n\u003e\n\u003e - **Redirect URIs:** Add `http://localhost:3000/auth/callback/zitadel` (for development)\n\u003e - **Post Logout Redirect URIs:** Add `http://localhost:3000/auth/logout/callback` (for development)\n\u003e\n\u003e These URLs must exactly match what your Express application uses. For production, add your production URLs.\n\n### Configuration\n\nTo run the application, you first need to copy the `.env.example` file to a new file named `.env` and fill in your ZITADEL application credentials.\n\n```dotenv\n# Port number where your Express server will listen for incoming HTTP requests.\n# Change this if port 3000 is already in use on your system.\nPORT=3000\n\n# Session timeout in seconds. Users will be automatically logged out after this\n# duration of inactivity. 3600 seconds = 1 hour.\nSESSION_DURATION=3600\n\n# Secret key used to cryptographically sign session cookies to prevent\n# tampering. MUST be a long, random string. Generate a secure key using:\n# node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\nSESSION_SECRET=\"your-very-secret-and-strong-session-key\"\n\n# Your ZITADEL instance domain URL. Found in your ZITADEL console under\n# instance settings. Include the full https:// URL.\n# Example: https://my-org-a1b2c3.zitadel.cloud\nZITADEL_DOMAIN=\"https://your-zitadel-domain\"\n\n# Application Client ID from your ZITADEL application settings. This unique\n# identifier tells ZITADEL which application is making the authentication\n# request.\nZITADEL_CLIENT_ID=\"your-client-id\"\n\n# While the Authorization Code Flow with PKCE for public clients\n# does not strictly require a client secret for OIDC specification compliance,\n# Auth.js will still require a value for its internal configuration.\n# Therefore, please provide a randomly generated string here.\n# You can generate a secure key using:\n# node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\nZITADEL_CLIENT_SECRET=\"your-randomly-generated-client-secret\"\n\n# OAuth callback URL where ZITADEL redirects after user authentication. This\n# MUST exactly match a Redirect URI configured in your ZITADEL application.\nZITADEL_CALLBACK_URL=\"http://localhost:3000/auth/callback/zitadel\"\n\n# URL where users are redirected after logout. This should match a Post Logout\n# Redirect URI configured in your ZITADEL application settings.\nZITADEL_POST_LOGOUT_URL=\"http://localhost:3000/auth/logout/callback\"\n\n# Optional. URL where users are redirected after successful login.\n# Defaults to \"/profile\" if not specified.\nZITADEL_POST_LOGIN_URL=\"/profile\"\n```\n\n### Installation and Running\n\nFollow these steps to get the application running:\n\n```bash\n# 1. Clone the repository\ngit clone git@github.com:zitadel/example-auth-expressjs.git\n\ncd example-auth-expressjs\n\n# 2. Install the project dependencies\nnpm install\n\n# 3. Start the development server\nnpm start\n# or\nmake start\n```\n\nThe application will now be running at `http://localhost:3000`.\n\n## Key Features\n\n### PKCE Authentication Flow\n\nThe application implements the secure Authorization Code Flow with PKCE (Proof Key for Code Exchange), which is the recommended approach for modern web applications.\n\n### Session Management\n\nBuilt-in session management with Auth.js handles user authentication state across your application, with automatic token refresh and secure session storage.\n\n### Route Protection\n\nProtected routes automatically redirect unauthenticated users to the login flow, ensuring sensitive areas of your application remain secure.\n\n### Logout Flow\n\nComplete logout implementation that properly terminates both the local session and the ZITADEL session, with proper redirect handling.\n\n## TODOs\n\n### 1. Security headers (Express middleware)\n\n**Not enabled.** Consider adding security headers middleware in your Express application:\n\n```javascript\nimport helmet from 'helmet';\n\napp.use(\n  helmet({\n    contentSecurityPolicy: {\n      directives: {\n        defaultSrc: [\"'self'\"],\n        scriptSrc: [\"'self'\", \"'unsafe-eval'\", \"'unsafe-inline'\"],\n      },\n    },\n  }),\n);\n```\n\nAt minimum, configure:\n\n- `Content-Security-Policy` (CSP)\n- `X-Frame-Options` / `frame-ancestors`\n- `Referrer-Policy`\n- `Permissions-Policy`\n\n## Resources\n\n- **Express Documentation:** \u003chttps://expressjs.com/\u003e\n- **Auth.js Documentation:** \u003chttps://authjs.dev/\u003e\n- **ZITADEL Documentation:** \u003chttps://zitadel.com/docs\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzitadel%2Fexample-auth-expressjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzitadel%2Fexample-auth-expressjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzitadel%2Fexample-auth-expressjs/lists"}