{"id":16750121,"url":"https://github.com/alexferl/echo-jwt","last_synced_at":"2025-03-16T04:20:29.383Z","repository":{"id":61839542,"uuid":"554294527","full_name":"alexferl/echo-jwt","owner":"alexferl","description":"JWT middleware for the Echo framework","archived":false,"fork":false,"pushed_at":"2024-02-06T18:01:39.000Z","size":56,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-22T16:48:44.606Z","etag":null,"topics":["echo","echo-jwt","echo-middleware","jwt","jwt-middleware","labstack-echo"],"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/alexferl.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}},"created_at":"2022-10-19T15:13:06.000Z","updated_at":"2024-02-05T00:03:52.000Z","dependencies_parsed_at":"2024-01-18T02:41:57.010Z","dependency_job_id":"a939280b-37f3-441d-985e-7d88279e2528","html_url":"https://github.com/alexferl/echo-jwt","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexferl%2Fecho-jwt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexferl%2Fecho-jwt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexferl%2Fecho-jwt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexferl%2Fecho-jwt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexferl","download_url":"https://codeload.github.com/alexferl/echo-jwt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243823312,"owners_count":20353648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["echo","echo-jwt","echo-middleware","jwt","jwt-middleware","labstack-echo"],"created_at":"2024-10-13T02:27:08.966Z","updated_at":"2025-03-16T04:20:29.362Z","avatar_url":"https://github.com/alexferl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# echo-jwt [![Go Report Card](https://goreportcard.com/badge/github.com/alexferl/echo-jwt)](https://goreportcard.com/report/github.com/alexferl/echo-jwt) [![codecov](https://codecov.io/gh/alexferl/echo-jwt/branch/master/graph/badge.svg)](https://codecov.io/gh/alexferl/echo-jwt)\n\nA [JWT](https://jwt.io/) middleware for the [Echo](https://github.com/labstack/echo) framework using\n[lestrrat-go/jwx](https://github.com/lestrrat-go/jwx).\n\n## Motivation\nYou might wonder why not use the JWT middleware that ships with Echo?\nThe reason is that it uses the [golang-jwt/jwt](https://github.com/golang-jwt/jwt) library which,\nalthough a good library, doesn't implement every JWT features while [lestrrat-go/jwx](https://github.com/lestrrat-go/jwx)\nis the [most complete](https://jwt.io/libraries?language=Go) implementation as of this writing.\nI think echo-jwt also has better defaults, like `RS256` as the default signing method and is also more flexible in what\nparsing options you can pass to the token verification function through the `Options` config.\nI think other features like `ExemptRoutes`, `ExemptMethods`, `OptionalRoutes` and `RefreshToken` are useful features\nthat most developers would want to use without having to implement them themselves.\n\n## Installing\n```shell\ngo get github.com/alexferl/echo-jwt\n```\n\n## Using\nBefore using the middleware you need to generate an RSA private key (RSASSA-PKCS-v1.5 using SHA-256) to\nsign and verify the tokens.\n\n```shell\nopenssl genrsa -out private-key.pem 4096\n```\n\n### Code example\n```go\npackage main\n\nimport (\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/alexferl/echo-jwt\"\n\t\"github.com/labstack/echo/v4\"\n\t\"github.com/lestrrat-go/jwx/v2/jwa\"\n\tjwx \"github.com/lestrrat-go/jwx/v2/jwt\"\n)\n\nvar privateKey *rsa.PrivateKey\n\nfunc main() {\n\te := echo.New()\n\n\te.GET(\"/\", func(c echo.Context) error {\n\t\tt := c.Get(\"token\").(jwx.Token)\n\t\treturn c.JSON(http.StatusOK, t)\n\t})\n\n\te.POST(\"/login\", func(c echo.Context) error {\n\t\tbuilder := jwx.NewBuilder().\n\t\t\tSubject(\"1\").\n\t\t\tIssuer(\"http://localhost:1323\").\n\t\t\tIssuedAt(time.Now()).\n\t\t\tNotBefore(time.Now()).\n\t\t\tExpiration(time.Now().Add(time.Minute*10)).\n\t\t\tClaim(\"name\", c.QueryParam(\"name\"))\n\n\t\ttoken, err := builder.Build()\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"failed building token: %v\\n\", err))\n\t\t}\n\n\t\tsigned, err := jwx.Sign(token, jwx.WithKey(jwa.RS256, privateKey))\n\t\tif err != nil {\n\t\t\tpanic(fmt.Sprintf(\"failed signing token: %v\\n\", err))\n\t\t}\n\n\t\treturn c.JSON(http.StatusOK, map[string]string{\"access_token\": string(signed)})\n\t})\n\n\tkey, err := loadPrivateKey(\"/path/to/private-key.pem\")\n\tif err != nil {\n\t\tpanic(fmt.Sprintf(\"failed loading private key: %v\\n\", err))\n\t}\n\tprivateKey = key\n\n\te.Use(jwt.JWT(key))\n\n\te.Logger.Fatal(e.Start(\"localhost:1323\"))\n}\n\nfunc loadPrivateKey(path string) (*rsa.PrivateKey, error) {\n\tf, err := os.Open(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tb, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tblock, _ := pem.Decode(b)\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse PEM block: %v\", err)\n\t}\n\n\tkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn key, nil\n}\n```\n\nGetting a token:\n```shell\ncurl -X POST http://localhost:1323/login\\?name\\=alex\n{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj...\"}\n```\n\nUsing a token:\n```shell\ncurl http://localhost:1323/ -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOj...'\n{\"exp\":1666320946,\"iat\":1666320346,\"iss\":\"http://localhost:1323\",\"name\":\"name\",\"nbf\":1666320346,\"sub\":\"1\"}\n```\n\n### Exempt routes\nBy default, *all* routes except `POST /login` will require a token in\nthe `Authorization` header or as a cookie with the key `access_token`.\n\nYou may define some additional exempted routes and methods that don't require a token:\n```go\ne.Use(jwt.JWTWithConfig(jwt.Config{\n\tExemptRoutes: map[string][]string{\n\t\t\"/\":          {http.MethodGet},\n\t\t\"/login\":     {http.MethodPost},\n\t\t\"/users\":     {http.MethodPost, http.MethodGet},\n\t\t\"/users/:id\": {http.MethodGet},\n\t},\n\tKey: key,\n}))\n```\n\n### Configuration\n```go\ntype Config struct {\n\t// Skipper defines a function to skip middleware.\n\tSkipper middleware.Skipper\n\n\t// Key defines the RSA key used to verify tokens.\n\t// Required.\n\tKey any\n\n\t// ExemptRoutes defines routes and methods that don't require tokens.\n\t// Optional. Defaults to /login [POST].\n\tExemptRoutes map[string][]string\n\n\t// ExemptMethods defines methods that don't require tokens.\n\t// Optional. Defaults to [OPTIONS].\n\tExemptMethods []string\n\n\t// OptionalRoutes defines routes and methods that\n\t// can optionally require a token.\n\t// Optional.\n\tOptionalRoutes map[string][]string\n\n\t// ParseTokenFunc defines a function used to decode tokens.\n\t// Optional.\n\tParseTokenFunc func(string, []jwt.ParseOption) (jwt.Token, error)\n\n\t// AfterParseFunc defines a function that will run after\n\t// the ParseTokenFunc has successfully run.\n\t// Optional.\n\tAfterParseFunc func(echo.Context, jwt.Token, string, TokenSource) *echo.HTTPError\n\n\t// Options defines jwt.ParseOption options for parsing tokens.\n\t// Optional. Defaults [jwt.WithValidate(true)].\n\tOptions []jwt.ParseOption\n\n\t// ContextKey defines the key that will be used to store the token\n\t// on the echo.Context when the token is successfully parsed.\n\t// Optional. Defaults to \"token\".\n\tContextKey string\n\n\t// CookieKey defines the key that will be used to read the token\n\t// from an HTTP cookie.\n\t// Optional. Defaults to \"access_token\".\n\tCookieKey string\n\n\t// AuthHeader defines the HTTP header that will be used to\n\t// read the token from.\n\t// Optional. Defaults to \"Authorization\".\n\tAuthHeader string\n\n\t// AuthScheme defines the authorization scheme in the AuthHeader.\n\t// Optional. Defaults to \"Bearer\".\n\tAuthScheme string\n\n\t// UseRefreshToken controls whether refresh tokens are used or not.\n\t// Optional. Defaults to false.\n\tUseRefreshToken bool\n\n\t// RefreshToken holds the configuration related to refresh tokens.\n\t// Optional.\n\tRefreshToken *RefreshToken\n}\n\ntype RefreshToken struct {\n\t// ContextKey defines the key that will be used to store the refresh token\n\t// on the echo.Context when the token is successfully parsed.\n\t// Optional. Defaults to \"refresh_token\".\n\tContextKey string\n\n\t// ContextKeyEncoded defines the key that will be used to store the encoded\n\t// refresh token on the echo.Context when the token is successfully parsed.\n\t// Optional. Defaults to \"refresh_token_encoded\".\n\tContextKeyEncoded string\n\n\t// CookieKey defines the key that will be used to read the refresh token\n\t// from an HTTP cookie.\n\t// Optional. Defaults to \"refresh_token\".\n\tCookieKey string\n\n\t// BodyMIMEType defines the expected MIME type of the request body.\n\t// Returns a 400 Bad Request if the request's Content-Type header does not match.\n\t// Optional. Defaults to \"application/json\".\n\tBodyMIMEType string\n\n\t// BodyKey defines the key that will be used to read the refresh token\n\t// from the request's body.\n\t// Returns a 422 UnprocessableEntity if the request's body key is missing.\n\t// Optional. Defaults to \"refresh_token\".\n\tBodyKey string\n\n\t// Routes defines routes and methods that require a refresh token.\n\t// Optional. Defaults to /auth/refresh [POST] and /auth/logout [POST].\n\tRoutes map[string][]string\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexferl%2Fecho-jwt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexferl%2Fecho-jwt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexferl%2Fecho-jwt/lists"}