{"id":13409060,"url":"https://github.com/adam-hanna/sessions","last_synced_at":"2026-01-12T10:14:52.631Z","repository":{"id":57493148,"uuid":"89755806","full_name":"adam-hanna/sessions","owner":"adam-hanna","description":"A dead simple, highly performant, highly customizable sessions middleware for go http servers.","archived":false,"fork":false,"pushed_at":"2023-07-17T20:28:46.000Z","size":90,"stargazers_count":78,"open_issues_count":4,"forks_count":11,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2025-08-14T01:32:58.335Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adam-hanna.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}},"created_at":"2017-04-29T01:09:28.000Z","updated_at":"2024-07-12T20:53:52.000Z","dependencies_parsed_at":"2024-01-08T14:30:45.624Z","dependency_job_id":"a36af3ab-ce2f-4b96-97bc-a3e621850e84","html_url":"https://github.com/adam-hanna/sessions","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/adam-hanna/sessions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-hanna%2Fsessions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-hanna%2Fsessions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-hanna%2Fsessions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-hanna%2Fsessions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adam-hanna","download_url":"https://codeload.github.com/adam-hanna/sessions/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adam-hanna%2Fsessions/sbom","scorecard":{"id":164675,"data":{"date":"2025-08-11","repo":{"name":"github.com/adam-hanna/sessions","commit":"76e553fb337883ce879e272efcbd00f5bf66951e"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","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":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"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":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/29 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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/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/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"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":"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":"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":"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":"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":"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":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'develop'"],"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":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"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-16T14:29:15.483Z","repository_id":57493148,"created_at":"2025-08-16T14:29:15.483Z","updated_at":"2025-08-16T14:29:15.483Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338062,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":[],"created_at":"2024-07-30T20:00:57.676Z","updated_at":"2026-01-12T10:14:52.609Z","avatar_url":"https://github.com/adam-hanna.png","language":"Go","funding_links":[],"categories":["Authentication and OAuth","身份验证和OAuth","Authentication and Authorization","Authentication \u0026 OAuth","认证和授权","認證和授權","Uncategorized","认证和OAuth授权","\u003cspan id=\"身份验证和oauth-authentication-and-auth\"\u003e身份验证和OAuth Authentication and Auth\u003c/span\u003e"],"sub_categories":["Contents"],"readme":"[![Build Status](https://travis-ci.org/adam-hanna/sessions.svg)](https://travis-ci.org/adam-hanna/sessions) [![Coverage Status](https://coveralls.io/repos/github/adam-hanna/sessions/badge.svg)](https://coveralls.io/github/adam-hanna/sessions) [![Go Report Card](https://goreportcard.com/badge/github.com/adam-hanna/sessions)](https://goreportcard.com/report/github.com/adam-hanna/sessions) [![GoDoc](https://godoc.org/github.com/adam-hanna/sessions?status.svg)](https://godoc.org/github.com/adam-hanna/sessions)\n\nIf you're interested in jwt's, see my [jwt library](https://github.com/adam-hanna/jwt-auth)!\n\n# Sessions\nA dead simple, highly performant, highly customizable sessions service for go http servers.\n\nBy default, the service stores sessions in redis, and transports sessions to clients in cookies. However, these are easily customizeable. For instance, the storage interface only implements three methods:\n\n~~~go\n// ServiceInterface defines the behavior of the session store\ntype ServiceInterface interface {\n\tSaveUserSession(userSession *user.Session) error\n\tDeleteUserSession(sessionID string) error\n\tFetchValidUserSession(sessionID string) (*user.Session, error)\n}\n~~~\n\n**README Contents:**\n\n1. [Quickstart](https://github.com/adam-hanna/sessions#quickstart)\n2. [Performance](https://github.com/adam-hanna/sessions#performance)\n3. [Design](https://github.com/adam-hanna/sessions#design)\n4. [API](https://github.com/adam-hanna/sessions#api)\n5. [Test Coverage](https://github.com/adam-hanna/sessions#test-coverage)\n6. [Example](https://github.com/adam-hanna/sessions#example)\n7. [License](https://github.com/adam-hanna/sessions#license)\n\n## Quickstart\n~~~go\nvar sesh *sessions.Service\n\n// issue a new session and write the session to the ResponseWriter\nuserSession, err := sesh.IssueUserSession(\"fakeUserID\", \"{\\\"foo\\\":\\\"bar\\\"}\", w)\nif err != nil {\n\tlog.Printf(\"Err issuing user session: %v\\n\", err)\n\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\treturn\n}\n\n...\n\n// Fetch a pointer to a valid user session from a request. A nil pointer indicates no or invalid session\nuserSession, err := sesh.GetUserSession(r)\nif err != nil {\n\tlog.Printf(\"Err fetching user session: %v\\n\", err)\n\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\treturn\n}\n// nil session pointers indicate a 401 unauthorized\nif userSession == nil {\n\thttp.Error(w, \"Unathorized\", http.StatusUnauthorized)\n\treturn\n}\n\n...\n\n// Extend session expiry. Note that session expiry's need to be manually extended\nif err := sesh.ExtendUserSession(userSession, r, w); err != nil {\n\tlog.Printf(\"Err extending user session: %v\\n\", err)\n\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\treturn\n}\n\n...\n\n// Invalidate a user session, deleting it from redis and expiring the cookie on the ResponseWriter\nif err := sesh.ClearUserSession(userSession, w); err != nil {\n\tlog.Printf(\"Err clearing user session: %v\\n\", err)\n\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\treturn\n}\n~~~\n\n## Performance\nBenchmarks require a redis-server running. Set the `REDIS_URL` environment variable, otherwise the benchmarks look for \":6379\".\n\nYMMV\n~~~ bash\n$ (cd benchmark \u0026\u0026 go test -bench=.)\n\nsetting up benchmark tests\nBenchmarkBaseServer-2              20000             72479 ns/op\nBenchmarkValidSession-2            10000            151650 ns/op\nPASS\nshutting down benchmark tests\nok      github.com/adam-hanna/sessions/benchmark        3.727s\n~~~\n\n## Design\nBy default, the service stores sessions in redis, and transports hashed sessionIDs to clients in cookies. However, these are easily customizeable through the creation of custom structs that implement the interface.\n\nThe general flow of the session service is as follows:\n\n1. Create [store](https://godoc.org/github.com/adam-hanna/sessions/store), [auth](https://godoc.org/github.com/adam-hanna/sessions/auth) and [transport](https://godoc.org/github.com/adam-hanna/sessions/transport) services by calling their respective `New(...)` functions (or create your own custom services that implement the service's interface methods). Then pass these services to the `sessions.New(...)` constructor.\n2. After a user logs in, call the `sessions.IssueUserSession(...)` function. This function first creates a new `user.Session`. SessionIDs are [RFC 4122 version 4 uuids](github.com/google/uuid). Next, the service hashes the sessionID with the provided key. The hashing algorithm is SHA-512, and therefore [the key used should be between 64 and 128 bytes](https://tools.ietf.org/html/rfc2104#section-3). Then, the service stores the session in redis and finally writes the hashed sessionID to the response writer in a cookie. Sessions written to the redis db utilize `EXPIREAT` to automatically destory expired sessions.\n3. To check if a valid session was included in a request, use the `sessions.GetUserSession(...)` function. This function grabs the hashed sessionID from the session cookie, verifies the HMAC signature and finally looks up the session in the redis db. If the session is expired, or fails HMAC signature verification, this function will return a nil pointer to a user session. If the session is valid, and you'd like to extend the session's expiry, you can then call `session.ExtendUserSession(...)`. Session expiry's are never automatically extended, only through calling this function will the session's expiry be extended.\n4. When a user logs out, call the `sessions.ClearUserSession(...)` function. This function destroys the session in the db and also destroys the cookie on the ResponseWriter.\n\n## API\n### [user.Session](https://godoc.org/github.com/adam-hanna/sessions/user#Session)\n~~~go\ntype Session struct {\n\tID        string\n\tUserID    string\n\tExpiresAt time.Time\n\tJSON      string\n}\n~~~\nSession is the struct that is used to store session data. The JSON field allows you to set any custom information you'd like. See the [example](https://github.com/adam-hanna/sessions#example)\n\n### [IssueUserSession](https://godoc.org/github.com/adam-hanna/sessions#IssueUserSession)\n~~~ go\nfunc (s *Service) IssueUserSession(userID string, json string, w http.ResponseWriter) (*user.Session, error)\n~~~\nIssueUserSession grants a new user session, writes that session info to the store and writes the session on the http.ResponseWriter.\n\nThis method should be called when a user logs in, for example.\n\n### [ClearUserSession](https://godoc.org/github.com/adam-hanna/sessions#ClearUserSession)\n~~~go\nfunc (s *Service) ClearUserSession(userSession *user.Session, w http.ResponseWriter) error\n~~~\nClearUserSession is used to remove the user session from the store and clear the cookies on the ResponseWriter.\n\nThis method should be called when a user logs out, for example.\n\n### [GetUserSession](https://godoc.org/github.com/adam-hanna/sessions#GetUserSession)\n~~~go\nfunc (s *Service) GetUserSession(r *http.Request) (*user.Session, error)\n~~~\nGetUserSession returns a user session from the hashed sessionID included in the request. This method only returns valid sessions. Therefore, sessions that have expired or that fail signature verification will return a nil pointer.\n\n### [ExtendUserSession](https://godoc.org/github.com/adam-hanna/sessions#ExtendUserSession)\n~~~go\nfunc (s *Service) ExtendUserSession(userSession *user.Session, r *http.Request, w http.ResponseWriter) error\n~~~\nExtendUserSession extends the ExpiresAt of a session by the Options.ExpirationDuration\n\nNote that this function must be called, manually! Extension of user session expiry's does not happen automatically!\n\n## Testing Coverage\n~~~bash\nok      github.com/adam-hanna/sessions\t\t\t9.012s  coverage: 94.1% of statements\nok      github.com/adam-hanna/sessions/auth\t\t0.003s  coverage: 100.0% of statements\nok      github.com/adam-hanna/sessions/store\t\t0.006s  coverage: 85.4% of statements\nok      github.com/adam-hanna/sessions/benchmark\t0.004s  coverage: 0.0% of statements [no tests to run]\nok      github.com/adam-hanna/sessions/transport\t0.004s  coverage: 95.2% of statements\nok      github.com/adam-hanna/sessions/user\t\t0.003s  coverage: 100.0% of statements\n~~~\n\nTests are broken down into three categories: unit, integration and e2e. Integration and e2e tests require a connection to a redis server. The connection address can be set in the `REDIS_URL` environment variable. The default is \":6379\".\n\nTo run all tests, simply:\n~~~\n$ go test -tags=\"unit integration e2e\" ./...\n\n// or\n$ make test\n\n// or\n$ make test-cover-html \u0026\u0026 go tool cover -html=coverage-all.out\n~~~\n\nTo run only tests from one of the categories:\n~~~\n$ go test -tags=\"integration\" ./...\n~~~\n\nTo run only unit and integration tests:\n~~~\n$ go test -tags=\"unit integration\" ./...\n~~~\n\n## Example\nThe following example is a demonstration of using the session service along with a CSRF code to check for authentication. The CSRF code is stored in the user.Session JSON field.\n\n~~~go\npackage main\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/adam-hanna/sessions\"\n\t\"github.com/adam-hanna/sessions/auth\"\n\t\"github.com/adam-hanna/sessions/store\"\n\t\"github.com/adam-hanna/sessions/transport\"\n)\n\n// SessionJSON is used for marshalling and unmarshalling custom session json information.\n// We're using it as an opportunity to tie csrf strings to sessions to prevent csrf attacks\ntype SessionJSON struct {\n\tCSRF string `json:\"csrf\"`\n}\n\nvar sesh *sessions.Service\n\nvar issueSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tcsrf, err := generateKey()\n\tif err != nil {\n\t\tlog.Printf(\"Err generating csrf: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tmyJSON := SessionJSON{\n\t\tCSRF: csrf,\n\t}\n\tJSONBytes, err := json.Marshal(myJSON)\n\tif err != nil {\n\t\tlog.Printf(\"Err marhsalling json: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tuserSession, err := sesh.IssueUserSession(\"fakeUserID\", string(JSONBytes[:]), w)\n\tif err != nil {\n\t\tlog.Printf(\"Err issuing user session: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlog.Printf(\"In issue; user's session: %v\\n\", userSession)\n\n\t// note: we set the csrf in a cookie, but look for it in request headers\n\tcsrfCookie := http.Cookie{\n\t\tName:     \"csrf\",\n\t\tValue:    csrf,\n\t\tExpires:  userSession.ExpiresAt,\n\t\tPath:     \"/\",\n\t\tHttpOnly: false,\n\t\tSecure:   false, // note: can't use secure cookies in development\n\t}\n\thttp.SetCookie(w, \u0026csrfCookie)\n\n\tw.WriteHeader(http.StatusOK)\n})\n\nvar requiresSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tuserSession, err := sesh.GetUserSession(r)\n\tif err != nil {\n\t\tlog.Printf(\"Err fetching user session: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\t// nil session pointers indicate a 401 unauthorized\n\tif userSession == nil {\n\t\thttp.Error(w, \"Unathorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\tlog.Printf(\"In require; user session expiration before extension: %v\\n\", userSession.ExpiresAt.UTC())\n\n\tmyJSON := SessionJSON{}\n\tif err := json.Unmarshal([]byte(userSession.JSON), \u0026myJSON); err != nil {\n\t\tlog.Printf(\"Err unmarshalling json: %v\\n\", err)\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlog.Printf(\"In require; user's custom json: %v\\n\", myJSON)\n\n\t// note: we set the csrf in a cookie, but look for it in request headers\n\tcsrf := r.Header.Get(\"X-CSRF-Token\")\n\tif csrf != myJSON.CSRF {\n\t\tlog.Printf(\"Unauthorized! CSRF token doesn't match user session\")\n\t\thttp.Error(w, \"Unauthorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\t// note that session expiry's need to be manually extended\n\tif err = sesh.ExtendUserSession(userSession, r, w); err != nil {\n\t\tlog.Printf(\"Err extending user session: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlog.Printf(\"In require; users session expiration after extension: %v\\n\", userSession.ExpiresAt.UTC())\n\n\t// need to extend the csrf cookie, too\n\tcsrfCookie := http.Cookie{\n\t\tName:     \"csrf\",\n\t\tValue:    csrf,\n\t\tExpires:  userSession.ExpiresAt,\n\t\tPath:     \"/\",\n\t\tHttpOnly: false,\n\t\tSecure:   false, // note: can't use secure cookies in development\n\t}\n\thttp.SetCookie(w, \u0026csrfCookie)\n\n\tw.WriteHeader(http.StatusOK)\n})\n\nvar clearSession = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\tuserSession, err := sesh.GetUserSession(r)\n\tif err != nil {\n\t\tlog.Printf(\"Err fetching user session: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\t// nil session pointers indicate a 401 unauthorized\n\tif userSession == nil {\n\t\thttp.Error(w, \"Unathorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tlog.Printf(\"In clear; session: %v\\n\", userSession)\n\n\tmyJSON := SessionJSON{}\n\tif err := json.Unmarshal([]byte(userSession.JSON), \u0026myJSON); err != nil {\n\t\tlog.Printf(\"Err unmarshalling json: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tlog.Printf(\"In require; user's custom json: %v\\n\", myJSON)\n\n\t// note: we set the csrf in a cookie, but look for it in request headers\n\tcsrf := r.Header.Get(\"X-CSRF-Token\")\n\tif csrf != myJSON.CSRF {\n\t\tlog.Printf(\"Unauthorized! CSRF token doesn't match user session\")\n\t\thttp.Error(w, \"Unauthorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tif err = sesh.ClearUserSession(userSession, w); err != nil {\n\t\tlog.Printf(\"Err clearing user session: %v\\n\", err)\n\t\thttp.Error(w, \"Internal Server Error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// need to clear the csrf cookie, too\n\taLongTimeAgo := time.Now().Add(-1000 * time.Hour)\n\tcsrfCookie := http.Cookie{\n\t\tName:     \"csrf\",\n\t\tValue:    \"\",\n\t\tExpires:  aLongTimeAgo,\n\t\tPath:     \"/\",\n\t\tHttpOnly: false,\n\t\tSecure:   false, // note: can't use secure cookies in development\n\t}\n\thttp.SetCookie(w, \u0026csrfCookie)\n\n\tw.WriteHeader(http.StatusOK)\n})\n\nfunc main() {\n\tseshStore := store.New(store.Options{})\n\n\t// e.g. `$ openssl rand -base64 64`\n\tseshAuth, err := auth.New(auth.Options{\n\t\tKey: []byte(\"DOZDgBdMhGLImnk0BGYgOUI+h1n7U+OdxcZPctMbeFCsuAom2aFU4JPV4Qj11hbcb5yaM4WDuNP/3B7b+BnFhw==\"),\n\t})\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tseshTransport := transport.New(transport.Options{\n\t\tHTTPOnly: true,\n\t\tSecure:   false, // note: can't use secure cookies in development!\n\t})\n\n\tsesh = sessions.New(seshStore, seshAuth, seshTransport, sessions.Options{})\n\n\thttp.HandleFunc(\"/issue\", issueSession)\n\thttp.HandleFunc(\"/require\", requiresSession)\n\thttp.HandleFunc(\"/clear\", clearSession) // also requires a valid session\n\n\tlog.Println(\"Listening on localhost:3000\")\n\tlog.Fatal(http.ListenAndServe(\"127.0.0.1:3000\", nil))\n}\n\n// thanks\n// https://astaxie.gitbooks.io/build-web-application-with-golang/en/06.2.html#unique-session-ids\nfunc generateKey() (string, error) {\n\tb := make([]byte, 16)\n\tif _, err := io.ReadFull(rand.Reader, b); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn base64.URLEncoding.EncodeToString(b), nil\n}\n~~~\n\n## License\n~~~\nThe MIT License (MIT)\n\nCopyright (c) 2017 Adam Hanna\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n~~~\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadam-hanna%2Fsessions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadam-hanna%2Fsessions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadam-hanna%2Fsessions/lists"}