{"id":37126797,"url":"https://github.com/ggicci/caddy-jwt","last_synced_at":"2026-03-09T01:08:57.009Z","repository":{"id":38316456,"uuid":"381587627","full_name":"ggicci/caddy-jwt","owner":"ggicci","description":"🆔 Caddy Module JWT Authentication","archived":false,"fork":false,"pushed_at":"2025-12-05T01:22:06.000Z","size":354,"stargazers_count":111,"open_issues_count":5,"forks_count":24,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-01-14T17:07:06.234Z","etag":null,"topics":["authentication","caddy-authentication","caddy-module","jwt","jwt-authentication"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ggicci.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"ggicci","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2021-06-30T05:39:36.000Z","updated_at":"2026-01-12T13:08:31.000Z","dependencies_parsed_at":"2024-01-14T19:47:38.816Z","dependency_job_id":"e536000d-65de-4e12-b7af-17727cfd28b0","html_url":"https://github.com/ggicci/caddy-jwt","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/ggicci/caddy-jwt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggicci%2Fcaddy-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggicci%2Fcaddy-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggicci%2Fcaddy-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggicci%2Fcaddy-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ggicci","download_url":"https://codeload.github.com/ggicci/caddy-jwt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ggicci%2Fcaddy-jwt/sbom","scorecard":{"id":425234,"data":{"date":"2025-08-11","repo":{"name":"github.com/ggicci/caddy-jwt","commit":"a0761d8311dcff8df6f3018f446050b275ae693b"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Code-Review","score":3,"reason":"Found 4/12 approved changesets -- score normalized to 3","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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":3,"reason":"2 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 3","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/go.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/ggicci/caddy-jwt/go.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/ggicci/caddy-jwt/go.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/go.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/ggicci/caddy-jwt/go.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction 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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"15 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3372 / GHSA-6wxm-mpqj-6jpf","Warn: Project is vulnerable to: GO-2024-2606 / GHSA-7jwh-3vrq-q3m8 / GHSA-mrww-27vc-gghv","Warn: Project is vulnerable to: GO-2024-2605 / GHSA-m7wr-2xf7-cm9p","Warn: Project is vulnerable to: GO-2024-2632 / GHSA-hj3v-m684-v259","Warn: Project is vulnerable to: GO-2024-2459 / GHSA-ppxx-5m9h-6vxf","Warn: Project is vulnerable to: GO-2024-2682 / GHSA-c33x-xqrf-c478","Warn: Project is vulnerable to: GO-2024-3302 / GHSA-px8v-pp82-rcvr","Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2024-2687 / GHSA-4v7x-pqxf-cx7m","Warn: Project is vulnerable to: GO-2024-3333","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2024-2611 / GHSA-8r3f-844c-mc37","Warn: Project is vulnerable to: GO-2024-2631 / GHSA-c5q2-7r4c-mv6g"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T02:06:36.852Z","repository_id":38316456,"created_at":"2025-08-19T02:06:36.852Z","updated_at":"2025-08-19T02:06:36.852Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30279768,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-08T20:45:49.896Z","status":"ssl_error","status_checked_at":"2026-03-08T20:45:49.525Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["authentication","caddy-authentication","caddy-module","jwt","jwt-authentication"],"created_at":"2026-01-14T14:42:07.031Z","updated_at":"2026-03-09T01:08:56.958Z","avatar_url":"https://github.com/ggicci.png","language":"Go","funding_links":["https://github.com/sponsors/ggicci"],"categories":[],"sub_categories":[],"readme":"# caddy-jwt\n\n![Go Workflow](https://github.com/ggicci/caddy-jwt/actions/workflows/go.yml/badge.svg) [![codecov](https://codecov.io/gh/ggicci/caddy-jwt/branch/main/graph/badge.svg?token=4V9OX8WFAW)](https://codecov.io/gh/ggicci/caddy-jwt) [![Go Report Card](https://goreportcard.com/badge/github.com/ggicci/caddy-jwt)](https://goreportcard.com/report/github.com/ggicci/caddy-jwt) [![Go Reference](https://pkg.go.dev/badge/github.com/ggicci/caddy-jwt.svg)](https://pkg.go.dev/github.com/ggicci/caddy-jwt)\n\nA Caddy HTTP Module - who Facilitates **JWT Authentication**\n\nThis module fulfilled [`http.handlers.authentication`](https://caddyserver.com/docs/modules/http.handlers.authentication) middleware as a provider named `jwt`.\n\n[Documentation](https://caddyserver.com/docs/modules/http.authentication.providers.jwt)\n\n## Install\n\nBuild this module with `caddy` at Caddy's official [download](https://caddyserver.com/download) site. Or build it with [xcaddy](https://github.com/caddyserver/xcaddy) locally by yourself:\n\n```bash\n# A caddy binary will be produced in your current directory.\nxcaddy build --with github.com/ggicci/caddy-jwt\n```\n\n**Requirements for a local build**:\n\n| caddy-jwt Version | Go version | Caddy Version |\n| ----------------- | ---------- | ------------- |\n| v1.1.1            | \u003e=1.25.0   | \u003e=2.10.1      |\n| v1.1.0            | \u003e=1.20.0   | \u003e=2.8.0       |\n\n\n## Sample Caddyfile\n\n```Caddyfile\n{\n\torder jwtauth before basicauth\n}\n\napi.example.com {\n\tjwtauth {\n\t\tsign_key TkZMNSowQmMjOVU2RUB0bm1DJkU3U1VONkd3SGZMbVk=\n\t\tsign_alg HS256\n\t\tjwk_url https://api.example.com/jwk/keys\n\t\tfrom_query access_token token\n\t\tfrom_header X-Api-Token\n\t\tfrom_cookies user_session\n\t\tissuer_whitelist https://api.example.com\n\t\taudience_whitelist https://api.example.io https://learn.example.com\n\t\tuser_claims aud uid user_id username login\n\t\tmeta_claims \"IsAdmin-\u003eis_admin\" \"settings.payout.paypal.enabled-\u003eis_paypal_enabled\"\n\t}\n\treverse_proxy http://172.16.0.14:8080\n}\n```\n\n**NOTE**:\n\n1. If you were using **symmetric** signing algorithms, e.g. `HS256`, encode your key bytes in `base64` format as `sign_key`'s value.\n\n```text\nTkZMNSowQmMjOVU2RUB0bm1DJkU3U1VONkd3SGZMbVk=\n```\n\n2. If you were using **asymmetric** signing algorithms, e.g. `RS256`, encode your public key in x.509 PEM format as `sign_key`'s value.\n\n```text\n-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArzekF0pqttKNJMOiZeyt\nRdYiabdyy/sdGQYWYJPGD2Q+QDU9ZqprDmKgFOTxUy/VUBnaYr7hOEMBe7I6dyaS\n5G0EGr8UXAwgD5Uvhmz6gqvKTV+FyQfw0bupbcM4CdMD7wQ9uOxDdMYm7g7gdGd6\nSSIVvmsGDibBI9S7nKlbcbmciCmxbAlwegTYSHHLjwWvDs2aAF8fxeRfphwQZKkd\nHekSZ090/c2V4i0ju2M814QyGERMoq+cSlmikCgRWoSZeWOSTj+rAZJyEAzlVL4z\n8ojzOpjmxw6pRYsS0vYIGEDuyiptf+ODC8smTbma/p3Vz+vzyLWPfReQY2RHtpUe\nhwIDAQAB\n-----END PUBLIC KEY-----\n```\n\n3. If you were using **JWK**, configure `jwk_url` and leave `sign_key` unset.\n\n4. `caddy-jwt` will determine the signing algorithm by looking into the following values:\n\n   1. `alg` value in the JWT header;\n   2. `alg` value of the matched JWK if using JWK;\n   3. value of the `sign_alg` config.\n\n5. The priority of `from_xxx` is `from_query \u003e from_header \u003e from_cookies`.\n\n6. Bypass the verification by turning on `skip_verification` option, [#85](/../../issues/85).\n\n7. Instead of specifying the `sign_key` directly as a value, you can use a feature introduced in Caddy v2.8.0 to load it from a file using `sign_key {file./path/to/sign_key.txt}`.\n\n## How to do integration test of caddy-jwt locally?\n\nFor **caddy-jwt users**, we assume you've already got a custom caddy binary built with our caddy-jwt plugin. Then you can run the test:\n\n```bash\necho '{\n        order jwtauth before basicauth\n}\n\n:8080 {\n        jwtauth {\n                sign_key TkZMNSowQmMjOVU2RUB0bm1DJkU3U1VONkd3SGZMbVk=\n                sign_alg HS256\n                from_query access_token token\n                from_header X-Api-Token\n                from_cookies user_session\n                user_claims aud uid user_id username login\n        }\n\n        respond \"User authenticated with ID: {http.auth.user.id}\"\n}' \u003e /tmp/caddy-jwt-test.Caddyfile\n\n# ./caddy is your custom caddy built, see Install section above\n./caddy run --config /tmp/caddy-jwt-test.Caddyfile\n\n# This token won't expire until year 2285.\nTEST_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjk5NTU4OTI2NzAsImp0aSI6IjgyMjk0YTYzLTk2NjAtNGM2Mi1hOGE4LTVhNjI2NWVmY2Q0ZSIsInN1YiI6IjM0MDYzMjc5NjM1MTY5MzIiLCJpc3MiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImF1ZCI6WyJodHRwczovL2FwaS5leGFtcGxlLmlvIl0sInVzZXJuYW1lIjoiZ2dpY2NpIn0.O8kvRO9y6xQO3AymqdFE7DDqLRBQhkntf78O9kF71F8\n\ncurl -v \"http://localhost:8080?access_token=${TEST_TOKEN}\"\n# You should see\n# 1. caddy log:\n#   http.authentication.providers.jwt       user authenticated      {\"token_string\": \"eyJhbGciOiJIUzI1…Qhkntf78O9kF71F8\", \"user_claim\": \"username\", \"id\": \"ggicci\"}\n#\n# 2. request response (curl command output):\n# User Authenticated with ID: ggicci\n\n# And the following command should also work:\ncurl -v -H\"X-Api-Token: ${TEST_TOKEN}\" \"http://localhost:8080\"\ncurl -v -H\"Authorization: Bearer ${TEST_TOKEN}\" \"http://localhost:8080\"\n```\n\n**NOTE**: you can decode the `${TEST_TOKEN}` above at [jwt.io](https://jwt.io/) to get human readable payload as follows:\n\n```json\n{\n  \"exp\": 9955892670,\n  \"jti\": \"82294a63-9660-4c62-a8a8-5a6265efcd4e\",\n  \"sub\": \"3406327963516932\",\n  \"iss\": \"https://api.example.com\",\n  \"aud\": [\"https://api.example.io\"],\n  \"username\": \"ggicci\"\n}\n```\n\nFor **caddy-jwt developers**, you need to clone this repo, and start the caddy server in the repo folder:\n\n```bash\ngit clone https://github.com/ggicci/caddy-jwt.git\ncd caddy-jwt\n\n# Build a caddy with this module and run an example server at localhost.\nxcaddy run --config /tmp/caddy-jwt-test.Caddyfile\n```\n\nAny local code changes should reflect immediately.\n\n## How caddy-jwt works?\n\nModule **caddy-jwt** behaves like a **\"JWT Validator\"**. The authentication flow is:\n\n```text\n   ┌──────────────────┐\n   │Extract token from│\n   │  1. query        │\n   │  2. header       │\n   │  3. cookies      │\n   └────────┬─────────┘\n            │\n    ┌───────▼───────────┐\n    │     is valid?     │\n    │  using `sign_key` │\n    │ or validation is  │\n    │     disabled      ├─NO───────┐\n    └───────┬───────────┘          │\n            │YES                   │\n┌───────────▼───────────┐          │\n│Populate {http.user.id}│          │\n│  by `user_claims`     │          │\n└───────────┬───────────┘          │\n            │                      │\n ┌──────────▼───────────┐          │\n │is {http.user.id} set?├──NO(empty)\n └──────────┬───────────┘       │  │\n            │YES(non-empty)     │  │\n ┌──────────▼───────────┐       │  │\n │Populate {http.user.*}│       │  │\n │   by `meta_claims`   │       │  │\n └──────────┬───────────┘       │  │\n            │                   │  │\n   ┌────────▼──────────┐ ┌──────▼──▼─────┐\n   │   Authenticated   │ │Unauthenticated│\n   │ Continue to Caddy │ │      401      │\n   └───────────────────┘ └───────────────┘\n```\n\nflowchart by https://asciiflow.com/\n\n## FAQ\n\n**Q1**: How to deal with 401 responses on OPTIONS requests? (CORS related)\n\nIt should be handled separately by Caddy. Please read [#24](https://github.com/ggicci/caddy-jwt/issues/24) for more details.\n\n**Q2**: What to note when using a public key as the value of `sign_key` in Caddyfile?\n\nUsing multi-line content in a directive [should be quoted](https://caddyserver.com/docs/caddyfile/concepts#tokens-and-quotes) as Caddy's documentation says. And the public key should be represented in PKCS#1 PEM format. Here's a simple command to derive such a public key from an RSA private key: `openssl rsa -in input.rsa -pubout`. Related: [#36](https://github.com/ggicci/caddy-jwt/issues/36).\n\n## Related Projects\n\n* https://github.com/steffenbusch/caddy-jwt-issuer: A Caddy plugin that issues JWT after username + password authentication\n\n## References\n\n- **MUST READ**: [JWT Security Best Practices](https://curity.io/resources/learn/jwt-best-practices/)\n- Online Debugers: http://jwt.io/, https://token.dev/jwt/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fggicci%2Fcaddy-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fggicci%2Fcaddy-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fggicci%2Fcaddy-jwt/lists"}