{"id":22458368,"url":"https://github.com/xenitab/go-oidc-middleware","last_synced_at":"2025-08-21T12:30:55.442Z","repository":{"id":37884545,"uuid":"392456286","full_name":"XenitAB/go-oidc-middleware","owner":"XenitAB","description":"OpenID Connect (OIDC) http middleware for Go","archived":false,"fork":false,"pushed_at":"2024-06-05T04:36:29.000Z","size":663,"stargazers_count":99,"open_issues_count":14,"forks_count":5,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-12-14T15:47:38.791Z","etag":null,"topics":["hacktoberfest","oidc","openid-connect"],"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/XenitAB.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":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-03T21:07:27.000Z","updated_at":"2024-12-02T23:51:20.000Z","dependencies_parsed_at":"2024-03-08T10:49:58.140Z","dependency_job_id":"8e6f12a7-b3b6-4ef1-a3cf-1622a2bb1f0f","html_url":"https://github.com/XenitAB/go-oidc-middleware","commit_stats":null,"previous_names":[],"tags_count":145,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XenitAB%2Fgo-oidc-middleware","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XenitAB%2Fgo-oidc-middleware/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XenitAB%2Fgo-oidc-middleware/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/XenitAB%2Fgo-oidc-middleware/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/XenitAB","download_url":"https://codeload.github.com/XenitAB/go-oidc-middleware/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230511482,"owners_count":18237658,"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":["hacktoberfest","oidc","openid-connect"],"created_at":"2024-12-06T08:12:23.378Z","updated_at":"2024-12-19T23:15:21.997Z","avatar_url":"https://github.com/XenitAB.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Go OpenID Connect (OIDC) HTTP Middleware\n\n[![Coverage Status](https://coveralls.io/repos/github/XenitAB/go-oidc-middleware/badge.svg)](https://coveralls.io/github/XenitAB/go-oidc-middleware)\n\n## Introduction\n\nThis is a middleware for http to make it easy to use OpenID Connect.\n\n## Changelog\n\nBelow, large (breaking) changes will be documented:\n\n### v0.0.42\n\nError handlers now have access to the request and have full control over the HTTP response sent to the client. Among other things, this can be used to return structured error responses a la [RFC7807](https://datatracker.ietf.org/doc/html/rfc7807). However, this comes with a breaking interface change: error handler contract changes from `type ErrorHandler func(description ErrorDescription, err error)` to `errorHandler(ctx context.Context, oidcErr *options.OidcError) *options.Response`. To retain the behavior of your error handler, update your error handler signature accordingly, change `description` to `oidcErr.Status`, `err` to `oidcErr.Error` and add an explicit `return nil`.\n\n### v0.0.41\n\nThe `oidcechojwt` adapter is replaced by `oidcecho` which brings in line with the other adapters in that it operates as a ordinary middleware. This makes all adapters full HTTP middlewares. That makes it easier to add new features, such as the the upcoming error handling changes. The new adapter provides identical features.\n\n### v0.0.37\n\nFrom `v0.0.37` and forward, the `options.WithRequiredClaims()` has been deprecated and generics are used to provide the claims type. A new validation function can be provided instead of `options.WithRequiredClaims()`. If you don't need claims validation, you can pass `nil` instead of a `ClaimsValidationFn`.\n\n## Stability notice\n\nThis library is under active development and the api will have breaking changes until `v0.1.0` - after that only breaking changes will be introduced between minor versions (`v0.1.0` -\u003e `v0.2.0`).\n\n## Currently tested providers\n\n- Azure AD\n- Auth0\n- Okta\n- Cognito\n\n## Currently Supported frameworks\n\n- [net/http](https://pkg.go.dev/net/http), [mux](https://github.com/gorilla/mux) \u0026 [chi](https://github.com/go-chi/chi)\n- [gin](https://github.com/gin-gonic/gin)\n- [fiber](https://github.com/gofiber/fiber)\n- [Echo](https://echo.labstack.com/)\n- Build your own middleware\n\n### Using options\n\nImport: `\"github.com/xenitab/go-oidc-middleware/options\"`\n\n### Claims validation example\n\nFrom `v0.0.37` and forward, claim validation is done using a `ClaimsValidationFn`. The below examples will use the following claims type and validation function:\n\n```go\ntype AzureADClaims struct {\n\tAio               string    `json:\"aio\"`\n\tAudience          []string  `json:\"aud\"`\n\tAzp               string    `json:\"azp\"`\n\tAzpacr            string    `json:\"azpacr\"`\n\tExpiresAt         time.Time `json:\"exp\"`\n\tIssuedAt          time.Time `json:\"iat\"`\n\tIdp               string    `json:\"idp\"`\n\tIssuer            string    `json:\"iss\"`\n\tName              string    `json:\"name\"`\n\tNotBefore         time.Time `json:\"nbf\"`\n\tOid               string    `json:\"oid\"`\n\tPreferredUsername string    `json:\"preferred_username\"`\n\tRh                string    `json:\"rh\"`\n\tScope             string    `json:\"scp\"`\n\tSubject           string    `json:\"sub\"`\n\tTenantId          string    `json:\"tid\"`\n\tUti               string    `json:\"uti\"`\n\tTokenVersion      string    `json:\"ver\"`\n}\n\nfunc GetAzureADClaimsValidationFn(requiredTenantId string) options.ClaimsValidationFn[AzureADClaims] {\n\treturn func(claims *AzureADClaims) error {\n\t\tif requiredTenantId != \"\" \u0026\u0026 claims.TenantId != requiredTenantId {\n\t\t\treturn fmt.Errorf(\"tid claim is required to be %q but was: %s\", requiredTenantId, claims.TenantId)\n\t\t}\n\n\t\treturn nil\n\t}\n}\n```\n\nIf you don't want typed claims, use `type Claims map[string]interface{}` and provide it. If you don't want to use a `ClaimsValidationFn` (as it will provide the type) the handlers will need to be configured as below:\n\n```go\ntype Claims map[string]interface{}\n\noidcHandler := oidchttp.New[Claims](h, nil, opts...)\n```\n\nor\n\n```go\noidcHandler := oidchttp.New[map[string]interface{}](h, nil, opts...)\n```\n\n### net/http, mux \u0026 chi\n\n**Import**\n\n`\"github.com/xenitab/go-oidc-middleware/oidchttp\"`\n\n**Middleware**\n\n```go\noidcHandler := oidchttp.New(h,\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithRequiredTokenType(\"JWT\"),\n\toptions.WithRequiredAudience(cfg.Audience),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n)\n```\n\n**Handler**\n\n```go\nfunc newClaimsHandler() http.HandlerFunc {\n\tfn := func(w http.ResponseWriter, r *http.Request) {\n\t\tclaims, ok := r.Context().Value(options.DefaultClaimsContextKeyName).(AzureADClaims)\n\t\tif !ok {\n\t\t\tw.WriteHeader(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\terr := json.NewEncoder(w).Encode(claims)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn http.HandlerFunc(fn)\n}\n```\n\n### gin\n\n**Import**\n\n`\"github.com/xenitab/go-oidc-middleware/oidcgin\"`\n\n**Middleware**\n\n```go\noidcHandler := oidcgin.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithRequiredTokenType(\"JWT\"),\n\toptions.WithRequiredAudience(cfg.Audience),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n)\n```\n\n**Handler**\n\n```go\nfunc newClaimsHandler() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tclaimsValue, found := c.Get(\"claims\")\n\t\tif !found {\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\tclaims, ok := claimsValue.(AzureADClaims)\n\t\tif !ok {\n\t\t\tc.AbortWithStatus(http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\tc.JSON(http.StatusOK, claims)\n\t}\n}\n```\n\n### fiber\n\n**Import**\n\n`\"github.com/xenitab/go-oidc-middleware/oidcfiber\"`\n\n**Middleware**\n\n```go\noidcHandler := oidcfiber.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithRequiredTokenType(\"JWT\"),\n\toptions.WithRequiredAudience(cfg.Audience),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n)\n```\n\n**Handler**\n\n```go\nfunc newClaimsHandler() fiber.Handler {\n\treturn func(c *fiber.Ctx) error {\n\t\tclaims, ok := c.Locals(\"claims\").(AzureADClaims)\n\t\tif !ok {\n\t\t\treturn c.SendStatus(fiber.StatusUnauthorized)\n\t\t}\n\n\t\treturn c.JSON(claims)\n\t}\n}\n```\n\n### Echo\n\n**Import**\n\n`\"github.com/xenitab/go-oidc-middleware/oidcecho\"`\n\n**Middleware**\n\n```go\noidcHandler := oidcecho.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithRequiredTokenType(\"JWT\"),\n\toptions.WithRequiredAudience(cfg.Audience),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n)\n```\n\n**Handler**\n\n```go\nfunc newClaimsHandler(c echo.Context) error {\n\tclaims, ok := c.Get(\"user\").(AzureADClaims)\n\tif !ok {\n\t\treturn echo.NewHTTPError(http.StatusUnauthorized, \"invalid token\")\n\t}\n\n\treturn c.JSON(http.StatusOK, claims)\n}\n```\n\n### Build your own middleware\n\n**Import**\n\n`\"github.com/xenitab/go-oidc-middleware/oidctoken\"`\n\n**Example**\n\n```go\noidcTokenHandler := oidctoken.New(h,\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithRequiredTokenType(\"JWT\"),\n\toptions.WithRequiredAudience(cfg.Audience),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n)\n\n// oidctoken.GetTokenString is optional, but you will need the JWT token as a string\ntokenString, err := oidctoken.GetTokenString(...)\nif err != nil {\n\tpanic(err)\n}\n\ntoken, err := oidcTokenHandler.ParseToken(ctx, tokenString)\nif err != nil {\n\tpanic(err)\n}\n```\n\n## Other options\n\n### Extract token from multiple headers\n\nExample for `Authorization` and `Foo` headers. If token is found in `Authorization`, `Foo` will not be tried. If `Authorization` extraction fails but there's a header `Foo = Bar_baz` then `baz` would be extracted as the token.\n\n```go\noidcHandler := oidcgin.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n\toptions.WithTokenString(\n\t\toptions.WithTokenStringHeaderName(\"Authorization\"),\n\t\toptions.WithTokenStringTokenPrefix(\"Bearer \"),\n\t),\n\toptions.WithTokenString(\n\t\toptions.WithTokenStringHeaderName(\"Foo\"),\n\t\toptions.WithTokenStringTokenPrefix(\"Bar_\"),\n\t),\n)\n```\n\n### Manipulate the token string after extraction\n\nIf you want to do any kind of manipulation of the token string after extraction, the option `WithTokenStringPostExtractionFn` is available.\n\nThe following would be used by a the Kubernetes api server, where the kubernetes client can use both `Authorization` and `Sec-WebSocket-Protocol`.\n\n```go\noidcHandler := oidcgin.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n\toptions.WithTokenString(\n\t\toptions.WithTokenStringHeaderName(\"Authorization\"),\n\t\toptions.WithTokenStringTokenPrefix(\"Bearer \"),\n\t),\n\toptions.WithTokenString(\n\t\toptions.WithTokenStringHeaderName(\"Sec-WebSocket-Protocol\"),\n\t\toptions.WithTokenStringTokenPrefix(\"base64url.bearer.authorization.k8s.io.\"),\n\t\toptions.WithTokenStringListSeparator(\",\"),\n\t\toptions.WithTokenStringPostExtractionFn(func(s string) (string, error) {\n\t\t\tbytes, err := base64.RawStdEncoding.DecodeString(s)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\n\t\t\treturn string(bytes), nil\n\t\t}),\n\t),\n)\n```\n\n### Custom error handler\n\nIt is possible to add a custom function to handle errors. The error handler can return an `options.Response` which will be rendered by the middleware. Returning `nil` will result in a default 400/401 error.\n\n```go\ntype Message struct {\n\tMessage string `json:\"message\"`\n\tUrl     string `json:\"url\"`\n}\n\nfunc errorHandler(ctx context.Context, oidcErr *options.OidcError) *options.Response {\n\tmessage := Message{\n\t\tMessage: string(oidcErr.Status),\n\t\tUrl:     oidcErr.Url.String(),\n\t}\n\tvar headers map[string]string\n\tjson, err := json.Marshal(message)\n\tif err != nil {\n\t\theaders[\"Content-Type\"] = \"text/plain\"\n\t\treturn \u0026options.Response{\n\t\t\tStatusCode: 500,\n\t\t\tHeaders:    headers,\n\t\t\tBody:       []byte(\"Internal encoding failure\\r\\n\"),\n\t\t}\n\t}\n\theaders[\"Content-Type\"] = \"text/plain\"\n\treturn \u0026options.Response{\n\t\tStatusCode: 418,\n\t\tHeaders:    headers,\n\t\tBody:       json,\n\t}\n}\n\noidcHandler := oidcgin.New(\n\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\toptions.WithIssuer(cfg.Issuer),\n\toptions.WithFallbackSignatureAlgorithm(cfg.FallbackSignatureAlgorithm),\n\toptions.WithErrorHandler(errorHandler),\n)\n```\n\nThis error handling interface was changed in v0.0.42. The previous interface was `func(description ErrorDescription, err error)`. In order to retain the same behavior, you need to update your error handler to read `desctiption` and `err` from `oidcErr` and return `nil`.\n\n### Testing with the middleware enabled\n\nThere's a small package that simulates an OpenID Provider that can be used with tests.\n\n```go\npackage main\n\nimport (\n\t\"testing\"\n\n\t\"github.com/xenitab/go-oidc-middleware/optest\"\n)\n\nfunc TestFoobar(t *testing.T) {\n\top := optest.NewTesting(t)\n\tdefer op.Close(t)\n\n\t[...]\n\n\toidcHandler := oidchttp.New(h,\n\t\tGetAzureADClaimsValidationFn(cfg.TenantID),\n\t\toptions.WithIssuer(op.GetURL(t)),\n\t\toptions.WithRequiredTokenType(\"JWT+AT\"),\n\t\toptions.WithRequiredAudience(\"test-client\"),\n\t)\n\n\ttoken := op.GetToken(t)\n\n\t[...]\n\n\ttoken.SetAuthHeader(req)\n\n\t[...]\n}\n```\n\nYou can also configure multiple users by setting the following:\n\n```go\nfunc TestFoobar(t *testing.T) {\n\ttestUsers := map[string]TestUser{\n\t\t\"test\": {\n\t\t\tAudience:           \"test-client\",\n\t\t\tSubject:            \"test\",\n\t\t\tName:               \"Test Testersson\",\n\t\t\tGivenName:          \"Test\",\n\t\t\tFamilyName:         \"Testersson\",\n\t\t\tLocale:             \"en-US\",\n\t\t\tEmail:              \"test@testersson.com\",\n\t\t\tAccessTokenKeyType: \"JWT+AT\",\n\t\t\tIdTokenKeyType:     \"JWT\",\n\t\t},\n\t\t\"foo\": {\n\t\t\tAudience:           \"foo-client\",\n\t\t\tSubject:            \"foo\",\n\t\t\tName:               \"Foo Bar\",\n\t\t\tGivenName:          \"Foo\",\n\t\t\tFamilyName:         \"Bar\",\n\t\t\tLocale:             \"en-US\",\n\t\t\tEmail:              \"foo@bar.com\",\n\t\t\tAccessTokenKeyType: \"JWT+AT\",\n\t\t\tIdTokenKeyType:     \"JWT\",\n\t\t},\n\t}\n\n\top := optest.NewTesting(t, optest.WithTestUsers(testUsers), optest.WithDefaultTestUser(\"test\"))\n\tdefer op.Close(t)\n\n\t[...]\n\n\ttoken1 := op.GetToken(t) // for user `test`\n\ttoken2 := op.GetTokenByUser(t, \"test\") // for user `test`\n\ttoken3 := op.GetTokenByUser(t, \"foo\") // for user `foo`\n}\n```\n\nIt is also possible to enable opaque access tokens with the option `optest.WithOpaqueAccessTokens()`. If you add `optest.WithLoginPrompt()` you will have a simple HTML page with the different test users to choose from when going to `/authorization`.\n\n## Examples\n\nSee [examples readme](examples/README.md) for more information.\n\n## Roadmap\n\n[GitHub Project](https://github.com/XenitAB/go-oidc-middleware/projects/1)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxenitab%2Fgo-oidc-middleware","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxenitab%2Fgo-oidc-middleware","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxenitab%2Fgo-oidc-middleware/lists"}