{"id":16444397,"url":"https://github.com/christopherrabotin/gin-contrib-headerauth","last_synced_at":"2025-12-16T00:10:00.944Z","repository":{"id":35570516,"uuid":"39842591","full_name":"ChristopherRabotin/gin-contrib-headerauth","owner":"ChristopherRabotin","description":"Middleware for the gin web framework. Allows to protect routes with a signature based authentication.","archived":false,"fork":false,"pushed_at":"2015-08-04T17:15:00.000Z","size":256,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2023-08-12T03:14:30.724Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChristopherRabotin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-28T15:35:29.000Z","updated_at":"2022-09-15T16:59:42.000Z","dependencies_parsed_at":"2022-08-31T22:40:51.393Z","dependency_job_id":null,"html_url":"https://github.com/ChristopherRabotin/gin-contrib-headerauth","commit_stats":null,"previous_names":[],"tags_count":1,"template":null,"template_full_name":null,"purl":"pkg:github/ChristopherRabotin/gin-contrib-headerauth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristopherRabotin%2Fgin-contrib-headerauth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristopherRabotin%2Fgin-contrib-headerauth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristopherRabotin%2Fgin-contrib-headerauth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristopherRabotin%2Fgin-contrib-headerauth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChristopherRabotin","download_url":"https://codeload.github.com/ChristopherRabotin/gin-contrib-headerauth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristopherRabotin%2Fgin-contrib-headerauth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271236280,"owners_count":24723978,"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","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":[],"created_at":"2024-10-11T09:24:16.985Z","updated_at":"2025-12-16T00:10:00.878Z","avatar_url":"https://github.com/ChristopherRabotin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ChristopherRabotin/gin-contrib-headerauth\n[![Build Status](https://travis-ci.org/ChristopherRabotin/gin-contrib-headerauth.svg?branch=master)](https://travis-ci.org/ChristopherRabotin/gin-contrib-headerauth) [![Coverage Status](https://coveralls.io/repos/ChristopherRabotin/gin-contrib-headerauth/badge.svg?branch=master\u0026service=github)](https://coveralls.io/github/ChristopherRabotin/gin-contrib-headerauth?branch=master)\n# Purpose\nAllows to protect routes with a header authentication, with a HMAC signature validation or without it.\n\n# Features\nQuite customizable, including the following custom settings.\n* Hash used for signature (e.g. SHA-1), cf `managers.Manager.HashFunction`.\n* Authorization header prefix (e.g. SAUTH), cf `managers.Manager.HeaderPrefix`.\n* Access key to secret key logic, header validation and data extraction for HMAC signature (e.g. hardcoded strings, database connection, etc.), cf `managers.Manager.CheckHeader`.\n* Allow unsigned requests, so they can be intercepted by another middleware for example, cf. `managers.Manager.HeaderRequired`.\n* Context key and value which can be used in the rest of the calls, cf. `managers.Manager.ContextKey` and cf. `managers.Manager.Authorize`.\n* Custom functions prior to an authentication failure (`managers.Manager.PreAbort`) and post success (`managers.Manager.PostAuth`).\n* Allow access on token in header only (without signature verification), cf `managers.TokenManager`.\n* Allow access HTTP Basic Auth , cf `managers.HTTPBasicAuth` and the [HTTP Basic Auth example](./example/httpbasicauth/).\n\n## Performance\nSince we're using Gin, the performance is quite blazing fast. Running the full test suite takes about 0.05 seconds on a 2013 Intel core i5.\n\n# Examples\nRefer to the [tests](./headerauth_test.go) and the [example](./example/) directory.\n\n# Quick start\n## Table of Contents\n+ [Access key and secret key authentication](./README.md#access-key-and-secret-authorization)\n\t+ [Code](./README.md#code)\n+ [Token authentication](./README.md#token-based-authorization)\n\t+ [Code](./README.md#code-1)\n+ [HTTP Basic authentication](./README.md#http-basic-authentication)\n\t+ [Code](./README.md#code-2)\n\n## Access key and secret authorization\n### Usage example\nServer *S* (running Gin) allows external parties to provide it information. We want to ensure that the data provided by the external party does come from\nthat external party, and is not vulnerable to [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).\n\n### Set up\n#### Key pair\nCreate a key pair of an access key and a secret key, both of which are provided to the external party, herein *E*.\n\nIn this example, we are using *static* access keys and secret keys, where the access key is `\"my_access_key\"` and the secret key is `\"super-secret-password\"`.\nA more concrete example, as commented below, will most likely use a database connection to store and retrieve access and secret keys.\n\n#### Signature protocol\nA signature protocol must be determined and known by the *S* and all the external parties (such as *E*) which will be doing requests to the endpoint.\n\nHere, we'll be implementing a *similar* signing method to the [Amazon AWS REST one](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html).\n##### Data to sign\nIn the following example, what is in curly brackets (`{` and `}`) corresponds to a variable. There will be an example below (new lines are `\\n`).\n```\n{REQUEST METHOD}\nhex_digest(md5_checksum({REQUEST BODY}))\niso_format({DATE TIME})\n```\nFor example, in the case of a POST request at `2015-08-03T19:24:21.807Z` where the body is set to `\"This is the body of my request.\"` and the secret is `\"super-secret-password\"`,\nthe signature should be `44393657f98352b9cfeb16f6152f1d02682c3885`. The signature data which led to this signature is as follows:\n```\nPOST\n6ed0e5471b9d353fab364c65f73f94f9\n2015-08-03T19:24:21.807Z\n```\n\n##### Headers\nA MIME-valid header must select to store the type authentication scheme used, the access key and the signature. Additional MIME-valid headers may be used for more data.\nIn this example, we'll also request *E* to set to `Date` header to the date at which the request was sent. Also in this example, any request which is older than fifteen\nminutes will be rejected.\n\n**Important note:** if your header is not MIME-valid, then Go [will not allow access to it](https://golang.org/src/net/textproto/reader.go). For example, mixed caps are only\nallowed if there is a dash before each uppercase letter (apart from the first letter of the header name): `AccessKey` is **invalid** but `Access-Key` is valid.\n\nFor example, let's say that the header which will contain the access key and signature is `Authorization` and the prefix prior to the access key and signature is `SAUTH`.\nThe prefix avoids conflict with other middlewares which read that same header. It also allows for support of several headerauth middleware on the same routes, but whose\nlogic is different based on the protocol (one could imagine an update to the protocol while still having to support old clients, e.g. `SAUTH` and `SAUTH2`).\n\nBuilding on the example above, we will set the following headers:\n* `Authorization` to `SAUTH my_access_key:44393657f98352b9cfeb16f6152f1d02682c3885`;\n* `Date` to `2015-08-03T19:24:21.807Z`.\n\n### Code\n#### Auth manager code\nWe'll start be defining a `struct` which details how the manager should work.\n\n**Note:** we'll be embedding the `HMACManager` struct from `managers.go` which massively simplifies the definition of an auth manager by already partially implementing the `Manager`\ninterface. If you need (or *want* for some odd reason) to write your full implementation of the `Manager` interface, check out [managers.go](./managers.go).\n\n```go\n// SHA384Manager is an example definition of an Manager struct.\ntype SHA384Manager struct {\n\t// --\u003e If using a database to check for the secret, you'll probably use a different struct, which may have a pointer\n\t// --\u003e to your database connection or even not set it, and have all the database connection, querying, and friends\n\t// --\u003e performed in the `CheckHeader` function.\n\tSecret string\n\t*headerauth.HMACManager\n}\n```\n\nWe now need to define how the backend should check that the access key is valid, what the expected secret key for this access key is, and especially ensure that the protocol agreed\nupon is respected (i.e. check that the Date header is within fifteen minutes, and build and return the expected string which will be signed).\nAll this is done in the `CheckHeader(string, *http.Request) (string, string, *AuthErr)` function.\n\n**Note:** it is good practice to have as little difference between error statuses throughout the verification process to avoid to play [Mastermind](https://en.wikipedia.org/wiki/Mastermind_%28board_game%29)\nwith a potential attacker.\n\n**Note:** `headerauth.AuthErr` will call the Gin context function `AbortWithError`, which will only return the error code to the client without any error message. The error message is only visible\nin the server logs.\n\n```go\n // CheckHeader returns the secret key and the data to sign from the provided access key.\n// Here should reside additional verifications on the header, or other parts of the request, if needed.\nfunc (m SHA384Manager) CheckHeader(auth *headerauth.AuthInfo, req *http.Request) (err *headerauth.AuthErr) {\n\tif req.ContentLength != 0 \u0026\u0026 req.Body == nil {\n\t\t// Not sure whether net/http or Gin handles these kinds of fun situations.\n\t\treturn \u0026headerauth.AuthErr{400, errors.New(\"received a forged packet\")}\n\t}\n\t// Grabbing the date and making sure it's in the correct format and is within fifteen minutes.\n\tdateHeader := req.Header.Get(\"Date\")\n\tif dateHeader == \"\" {\n\t\treturn \u0026headerauth.AuthErr{406, errors.New(\"no Date header provided\")}\n\t}\n\tdate, derr := time.Parse(\"2006-01-02T15:04:05.000Z\", dateHeader)\n\tif derr != nil {\n\t\treturn \u0026headerauth.AuthErr{408, errors.New(\"could not parse date\")}\n\t} else if time.Since(date) \u003e time.Minute*15 {\n\t\treturn \u0026headerauth.AuthErr{410, errors.New(\"request is too old\")}\n\t}\n\n\t// --\u003e Here is where you would do a database call to check if the access key is valid\n\t// --\u003e and what the appropriate secret key is, e.g.:\n\t// if secretKey, dbErr := getSecretFromDB(access); dbErr == nil \u0026\u0026 auth.Secret == secretKey { ...\n\tif auth.AccessKey == \"my_access_key\" {\n\t\t// In this example, we'll be implementing a *similar* signing method to the Amazon AWS REST one.\n\t\t// We'll use the HTTP-Verb, the MD5 checksum of the Body, if any, and the Date header in ISO format.\n\t\t// http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html\n\t\t// Note: We are returning a variety of error codes which don't follow the spec only for the purpose of testing.\n\t\tserializedData := req.Method + \"\\n\"\n\t\tif req.ContentLength != 0 {\n\t\t\tbody, err := ioutil.ReadAll(req.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn \u0026headerauth.AuthErr{402, errors.New(\"could not read the body\")}\n\t\t\t}\n\t\t\thash := md5.New()\n\t\t\thash.Write(body)\n\t\t\tserializedData += hex.EncodeToString(hash.Sum(nil)) + \"\\n\"\n\t\t} else {\n\t\t\tserializedData += \"\\n\"\n\t\t}\n\t\t// We know from Authorize that the Date header is present and fits our time constaints.\n\t\tserializedData += req.Header.Get(\"Date\")\n\n\t\tauth.Secret = m.Secret\n\t\tauth.DataToSign = serializedData\n\t\treturn\n\t}\n\treturn \u0026headerauth.AuthErr{418, errors.New(\"you are a teapot\")}\n}\n```\n\nFinally, we only need to define the value to store in the Gin context for a valid authentication. This will be useful for subsequent parts of the\ncode which perform tasks based on the valid access key.\n\n```go\n// Authorize returns the value to store in Gin's context at ContextKey().\n// This is only called once the requested has been authorized to pursue,\n// so logging of success should happen here.\nfunc (m SHA384Manager) Authorize(auth *headerauth.AuthInfo) (interface{}, *headerauth.AuthErr) {\n\tif auth.AccessKey == \"my_access_key\" {\n\t\treturn \"All good with my access key!\", nil\n\t}\n\treturn \"All good with any access key!\", nil\n}\n```\n\n### Defaults\nSince `SHA384Manager` embeds the `HMACManager`, the following defaults apply:\n* Header name where the access key and signature should be: `Authorization`\n* Hash function used for signing the data with the secret key [SHA384](https://en.wikipedia.org/wiki/SHA-2) (`sha512.New384` in Go).\n* Header separator between the access key and the signature is a colon `:`. This must be a character which **cannot** be found in the access key.\n\n### Setting the auth manager as a middleware\nIn the main Gin router, you must initialize and set this created auth manager.\n```go\nfunc main() {\n\t// Setting the secret to \"super-secret-password\".\n\t// Setting the header prefix to `SAUTH`, and the context key in Gin to be called `contextKey`.\n\tmgr := SHA384Manager{\"super-secret-password\", headerauth.NewHMACSHA384Manager(\"SAUTH\", \"contextKey\")}\n\trouter := gin.Default()\n\trouter.Use(headerauth.SignatureAuth(mgr))\n\trouter.POST(\"/test/\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"Success.\")\n\t})\n\trouter.PUT(\"/test/\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"Success.\")\n\t})\n\trouter.Run(\"localhost:31337\")\n}\n```\n\n## Token based authorization\n### Usage example\nServer *S* (running Gin) allows external parties to provide it information based on very simple auth scheme where only a unique token is used.\nFor example, a (large) list of valid tokens can be provided to an external party, *E*, which only needs to specify one of those per request in\norder to be granted access. \n\n### Set up\nThe set up is trivial for this scheme because there is no signature involved.\n\n#### Tokens\nA list of valid tokens must be provided to *E*.\n\n#### Headers\nIt should be agreed what the headers should be. For example, we can expect the header to be `X-Token-Auth` and the prefix to be `Token`.\n\n### Code\n#### Manager code\n```go\n// TMgr is an example definition of an AuthKeyManager struct.\ntype TMgr struct {\n\t*headerauth.TokenManager\n}\n\n// Authorize returns the secret key from the provided access key.\nfunc (m TMgr) CheckHeader(auth *AuthInfo, req *http.Request) (err *AuthErr) {\n\tauth.Secret = \"\"     // There is no secret key, just an access key.\n\tauth.DataToSign = \"\" // There is no data to sign in Token auth.\n\tif auth.AccessKey != \"valid\" {\n\t\terr = \u0026AuthErr{403, errors.New(\"invalid access key\")}\n\t}\n\treturn\n}\n\nfunc (m TMgr) Authorize(auth *AuthInfo) (val interface{}, err *AuthErr) {\n\treturn true, nil\n}\n```\n\n#### Defaults\n* Required is `true` meaning that if the authentication fails, the request will abort.\n\n#### Setting the auth manager as a middleware\n```go\nfunc main() {\n\t// Setting the Gin context key to \"accessKey\". \n\tmgr := TMgr{NewTokenManager(\"X-Token-Auth\", \"Token\", \"accessKey\")}\n\trouter := gin.Default()\n\trouter.Use(SignatureAuth(mgr))\n\tmethods := []string{\"GET\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\"}\n\tfor _, meth := range methods {\n\t\trouter.Handle(meth, \"/tokenTest/\", []gin.HandlerFunc{func(c *gin.Context) {\n\t\t\tc.String(http.StatusOK, \"Success.\")\n\t\t}}[0])\n\t}\n}\n```\n\n### Header example\nWith the previously defined manager, the following auth header would be valid.\n* `X-Token-Auth`: `Token MyValidTokenWhichOnlyIKnow!`\n\n## HTTP Basic Authentication\n[HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication) is an **insecure** auth scheme. However, we have it as an example here\nbecause it's commonly used, and it demonstrates a good usage of the `PreAbort` function called just prior to aborting the Gin request if there is an\nauthentification failure. In this case, we'll be setting a custom header.\n\n### Usage example\nServer *S* has a list of valid username and passwords. For some obscure reason, it is required to use HTTP Basic Auth, maybe because it is commonly\nsupported by browsers.\n\n### Code\n#### Manager code\nThe simplest, as with the other schemes, is to embed the helping struct, in this case `headerauth.HTTPBasicAuth`.\n\nFor `headerauth.HTTPBasicAuth`, the username and password checking happens in the `Authorize` function. In the following example, we use a `map[string]string`\nbut a more prod-like implementation would surely use a database connection to check that the user provided exists, and the password matches.\n\n**Note:** When the `Authorize` function is reached, the username is stored in `auth.AccessKey` and the password in `auth.Secret`\n(cf `(m HTTPBasicAuth) CheckHeader(auth *AuthInfo, req *http.Request) (err *AuthErr)` in  `managers.go`).\n\n```go\n// HTTPBasicDemo is an example of an HTTP Basic Auth.\ntype HTTPBasicDemo struct {\n\tAccounts map[string]string // --\u003e Here we are using a hard coded map, but the logic is up to the dev.\n\t*headerauth.HTTPBasicAuth // Embedded struct greatly helps in defining HTTP Basic Auth.\n}\n\n// Authorize checks that the provided authorization is valid.\n// --\u003e Here is where you can interface with a database, or something which stores the list of valid usernames\n// --\u003e and their associated passwords. Note that in the other schemes we try to fail earlier (in CheckHeader).\nfunc (m HTTPBasicDemo) Authorize(auth *headerauth.AuthInfo) (val interface{}, err *headerauth.AuthErr) {\n\tif password, ok := m.Accounts[auth.AccessKey]; !ok || password != auth.Secret {\n\t\terr = \u0026headerauth.AuthErr{401, errors.New(\"invalid credentials\")}\n\t} else {\n\t\t// In CheckHeader we changed the AccessKey to be the actual username, instead\n\t\t// of the Base64 encoded authentication string.\n\t\tval = auth.AccessKey\n\t}\n\treturn\n}\n```\n\n#### Setting the auth manager as a middleware\nAs usual, this is trivial.\n```go\nfunc main() {\n\tmgr := HTTPBasicDemo{Accounts: map[string]string{\"user\": \"password\"}, HTTPBasicAuth: headerauth.NewHTTPBasicAuthManager(\"user\", \"My Protected Group\")}\n\trouter := gin.Default()\n\trouter.Use(headerauth.HeaderAuth(mgr))\n\trouter.GET(\"/test/\", func(c *gin.Context) {\n\t\tc.String(200, \"Success.\")\n\t})\n\trouter.Run(\"localhost:31337\")\n}\n```\n\n### Header example\nWith an HTTP Basic Auth manager, and with the example above, you'll get 200 Success with the following header.\n* `Authorization`:`Basic dXNlcjpwYXNzd29yZA==`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopherrabotin%2Fgin-contrib-headerauth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristopherrabotin%2Fgin-contrib-headerauth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristopherrabotin%2Fgin-contrib-headerauth/lists"}