{"id":19344346,"url":"https://github.com/egregors/passkey","last_synced_at":"2025-04-23T04:36:19.974Z","repository":{"id":248158398,"uuid":"809032872","full_name":"egregors/passkey","owner":"egregors","description":"🔑 Go library for implementing WebAuthn services 🗝️","archived":false,"fork":false,"pushed_at":"2025-04-15T09:07:37.000Z","size":1920,"stargazers_count":16,"open_issues_count":7,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-15T09:42:27.415Z","etag":null,"topics":["golang","passkey","webauthn"],"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/egregors.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","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,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-01T13:50:13.000Z","updated_at":"2025-04-15T09:07:37.000Z","dependencies_parsed_at":"2024-07-12T19:44:30.145Z","dependency_job_id":"f3820669-b958-4c1c-9571-1e03372e4fc5","html_url":"https://github.com/egregors/passkey","commit_stats":null,"previous_names":["egregors/passkey"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egregors%2Fpasskey","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egregors%2Fpasskey/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egregors%2Fpasskey/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egregors%2Fpasskey/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/egregors","download_url":"https://codeload.github.com/egregors/passkey/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250372421,"owners_count":21419718,"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":["golang","passkey","webauthn"],"created_at":"2024-11-10T03:43:12.727Z","updated_at":"2025-04-23T04:36:19.962Z","avatar_url":"https://github.com/egregors.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003ch1\u003e🔑 passkey\u003c/h1\u003e\n\n`passkey` is a Go library for implementing WebAuthn services\n\n[![Build Status](https://github.com/egregors/passkey/workflows/build/badge.svg)](https://github.com/egregors/passkey/actions)\n[![Go Report Card](https://goreportcard.com/badge/github.com/egregors/passkey)](https://goreportcard.com/report/github.com/egregors/passkey)\n[![Coverage Status](https://coveralls.io/repos/github/egregors/passkey/badge.svg?branch=main)](https://coveralls.io/github/egregors/passkey?branch=main)\n[![godoc](https://godoc.org/github.com/egregors/passkey?status.svg)](https://godoc.org/github.com/egregors/passkey)\n\n\u003c/div\u003e\n\n---\n\n## Table of Contents\n\n\u003c!-- TOC --\u003e\n\n* [Table of Contents](#table-of-contents)\n* [Features](#features)\n* [Installation](#installation)\n* [Usage](#usage)\n    * [Library Usage](#library-usage)\n        * [Implement storages: `UserStore` and two\n          `SessionStore` one for WebAuthn and one for general session](#implement-storages-userstore-and-two-sessionstore-one-for-webauthn-and-one-for-general-session)\n        * [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)\n        * [Client-side](#client-side)\n    * [Example Application](#example-application)\n* [API](#api)\n    * [Middleware](#middleware)\n* [Development](#development)\n    * [Common tasks](#common-tasks)\n    * [Mocks](#mocks)\n    * [Troubleshooting](#troubleshooting)\n        * [FAQ](#faq)\n* [Contributing](#contributing)\n* [License](#license)\n\n\u003c!-- TOC --\u003e\n\n## Features\n\n- **User Management**: Handle user information and credentials.\n- **WebAuthn Integration**: Easily integrate with WebAuthn for authentication.\n- **Session Management**: Manage user sessions securely.\n- **Middleware Support**: Middleware for authenticated routes.\n\n\u003e [!NOTE]\n\u003e In general, this library is built on top of two open-source solutions:\n\u003e * Golang WebAuthn Library – https://github.com/go-webauthn/webauthn\n\u003e * JS/TS SimpleWebAuthn client – https://github.com/MasterKale/SimpleWebAuthn\n\nUsed in project:\n![Static Badge](https://img.shields.io/badge/Go_WebAuthn-v0.12.1-green)\n![Static Badge](https://img.shields.io/badge/TS%5CJS%20SimpleWebAuthn-v13.1.0-green)\n\nActual versions:\n![GitHub Release](https://img.shields.io/github/v/release/go-webauthn/webauthn?label=Go%20WebAuthn)\n![GitHub Release](https://img.shields.io/github/v/release/MasterKale/SimpleWebAuthn?label=TS%5CJS%20SimpleWebAuthn)\n\n## Installation\n\nTo get started, you need to have Go installed on your machine. If you don't have it installed,\nyou can download it from [here](https://golang.org/dl/).\n\nInstall the library using `go get`:\n\n```bash\ngo get github.com/egregors/passkey\n```\n\n## Usage\n\n### Library Usage\n\nTo add a passkey service to your application, you need to do a few simple steps:\n\n#### Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session\n\n```go\npackage passkey\n\nimport \"github.com/go-webauthn/webauthn/webauthn\"\n\n// UserStore is a persistent storage for users and credentials\ntype UserStore interface {\n\tCreate(username string) (User, error)\n\tUpdate(User) error\n\n\tGet(userID []byte) (User, error)\n\tGetByName(username string) (User, error)\n}\n\n// SessionStore is a storage for session data\ntype SessionStore[T webauthn.SessionData | UserSessionData] interface {\n\tCreate(data T) (string, error)\n\tDelete(token string)\n\n\tGet(token string) (*T, bool)\n}\n\n```\n\nYour `User` model also should implement `User` interface:\n\n```go\npackage main\n\nimport \"github.com/go-webauthn/webauthn/webauthn\"\n\n// User is a user with webauthn credentials\ntype User interface {\n\twebauthn.User\n\tPutCredential(webauthn.Credential)\n}\n\n```\n\nThis interface is an extension of the `webauthn.User` interface. It adds a `PutCredential` method that allows you to\nstore a credential in the user object.\n\n#### Create a new `Passkey` instance and mount the routes\n\nThe whole example is in `_example` directory.\n\n```go\npackage main\n\nimport (\n\t\"embed\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/go-webauthn/webauthn/webauthn\"\n\n\t\"github.com/egregors/passkey\"\n\t\"github.com/egregors/passkey/log\"\n)\n\n//go:embed web/*\nvar webFiles embed.FS\n\nconst userKey passkey.AuthUserIDKey = \"pkUser\"\n\nfunc main() {\n\tproto := getEnv(\"PROTO\", \"http\")             // \"http\" | \"https\"\n\tsub := getEnv(\"SUB\", \"\")                     // \"\" | \"login.\"\n\thost := getEnv(\"HOST\", \"localhost\")          // \"localhost\" | \"example.com\"\n\toriginPort := getEnv(\"ORIGIN_PORT\", \":8080\") // \":8080\" | \"\" if you use reverse proxy it should be the most \"external\" port\n\tserverPort := getEnv(\"SERVER_PORT\", \":8080\") // \":8080\"\n\n\torigin := fmt.Sprintf(\"%s://%s%s%s\", proto, sub, host, originPort)\n\n\tl := log.NewLogger()\n\n\tpkey, err := passkey.New(\n\t\tpasskey.Config{\n\t\t\tWebauthnConfig: \u0026webauthn.Config{\n\t\t\t\tRPDisplayName: \"Passkey Example\", // Display Name for your site\n\t\t\t\tRPID:          host,              // Generally the FQDN for your site\n\t\t\t\tRPOrigins:     []string{origin},  // The origin URLs allowed for WebAuthn\n\t\t\t},\n\t\t\tUserStore:        NewUserStore(),\n\t\t\tAuthSessionStore: NewSessionStore[webauthn.SessionData](),\n\t\t\tUserSessionStore: NewSessionStore[passkey.UserSessionData](),\n\t\t},\n\t\tpasskey.WithLogger(l),\n\t\tpasskey.WithUserSessionMaxAge(60*time.Minute),\n\t\tpasskey.WithSessionCookieNamePrefix(\"passkeyDemo\"),\n\t\tpasskey.WithInsecureCookie(), // In order to support Safari on localhost. Do not use in production.\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tmux := http.NewServeMux()\n\n\t// mount the passkey routes\n\tpkey.MountRoutes(mux, \"/api/\")\n\n\t// public routes\n\tweb, _ := fs.Sub(webFiles, \"web\")\n\tmux.Handle(\"/\", http.FileServer(http.FS(web)))\n\tmux.HandleFunc(\"/logout\", func(w http.ResponseWriter, r *http.Request) {\n\t\tpkey.Logout(w, r)\n\t\thttp.Redirect(w, r, \"/\", http.StatusSeeOther)\n\t})\n\n\t// private routes\n\tprivateMux := http.NewServeMux()\n\tprivateMux.HandleFunc(\"/\", privateHandler())\n\n\t// wrap the privateMux with the Auth middleware\n\twithAuth := pkey.Auth(\n\t\tuserKey,\n\t\tnil,\n\t\tpasskey.RedirectUnauthorized(url.URL{Path: \"/\"}),\n\t)\n\tmux.Handle(\"/private\", withAuth(privateMux))\n\n\t// start the server\n\tl.Infof(\"Listening on %s\\n\", origin)\n\tif err := http.ListenAndServe(serverPort, mux); err != nil { //nolint:gosec\n\t\tpanic(err)\n\t}\n}\n\n```\n\nYou can optionally provide a logger to the `New` function using the `WithLogger` option.\n\nFull list of options:\n\n| Name                        | Default                                | Description                                          |\n|-----------------------------|----------------------------------------|------------------------------------------------------|\n| WithLogger                  | NullLogger                             | Provide custom logger                                |\n| WithInsecureCookie          | Disabled (cookie is secure by default) | Sets Cookie.Secure to false                          |\n| WithSessionCookieNamePrefix | `pk`                                   | Sets the name prefix of the session and user cookies |\n| WithUserSessionMaxAge       | 60 minutes                             | Sets the max age of the user session cookie          |\n\n#### Client-side\n\nYou need a client-side library that can be used to interact with the server-side library. In example app we use\n[SimpleWebAuthn](https://github.com/MasterKale/SimpleWebAuthn) library (check `_example/web` directory).\n\n### Example Application\n\nThe library comes with an example application that demonstrates how to use it. To run the example application\njust run the following command:\n\n```bash\n# go run local example app on http://localhost:8080 (no ssl)\nmake run \n```\n\nor\n\n```bash\n# run example app in docker container on https://localhost (with self-signed certificate)\nmake up \n```\n\n## API\n\n| Method                                                                                            | Description                                                                                         |\n|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `New(cfg Config, opts ...Option) (*Passkey, error)`                                               | Creates a new Passkey instance.                                                                     |\n| `MountRoutes(mux *http.ServeMux, path string)`                                                    | Mounts the Passkey routes onto a given HTTP multiplexer.                                            |\n| `Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler` | Middleware to protect routes that require authentication.                                           |\n| `UserIDFromCtx(ctx context.Context, pkUserKey string) ([]byte, bool)`                             | Returns the user ID from the request context. If the userID is not found, it returns nil and false. |\n\n### Middleware\n\nThe library provides a middleware function that can be used to protect routes that require authentication.\n\n```go\nAuth(userIDKey string, onSuccess, onFail http.HandlerFunc) func (next http.Handler) http.Handler\n```\n\nIt takes key for context and two callback functions that are called when the user is authenticated or not.\nYou can use the context key to retrieve the authenticated userID from the request context\nwith `passkey.UserIDFromCtx`.\n\n`passkey` contains a helper function:\n\n| Helper                       | Description                                                             |\n|------------------------------|-------------------------------------------------------------------------|\n| Unauthorized                 | Returns a 401 Unauthorized response when the user is not authenticated. |\n| RedirectUnauthorized(target) | Redirects the user to a given URL when they are not authenticated.      |\n| UserFromContext              | Get userID from context                                                 |\n\nYou can use it to protect routes that require authentication:\n\n```go\npackage main\n\nimport (\n\t\"net/url\"\n\n\t\"github.com/egregors/passkey\"\n)\n\nfunc main() {\n\tpkey, err := passkey.New(...)\n\tcheck(err)\n\n\twithAuth := pkey.Auth(\n\t\t\"pkUser\",\n\t\tnil,\n\t\tpasskey.RedirectUnauthorized(url.URL{Path: \"/\"}),\n\t)\n\n\tmux.Handle(\"/private\", withAuth(privateMux))\n}\n\n```\n\n## Development\n\n### Common tasks\n\nTo common dev task just use `make`:\n\n```bash\n➜  passkey git:(main) make help\nUsage: make [task]\n\ntask                 help\n------               ----\n                     \nlint                 Lint the files\ntest                 Run unittests\nrun                  Run example project\nup                   Run example project with local SSL (self-signed certificate)\ngen                  Generate mocks\nupdate-go-deps       Updating Go dependencies\n                     \nhelp                 Show help message\n\n```\n\n### Mocks\n\nUse [mockery](https://github.com/vektra/mockery) to generate mocks for interfaces.A\n\n### Troubleshooting\n\n#### FAQ\n\n**Q: I'm getting an error \"named cookie not present\" when I try to interact with passkey running on `localhost` from\nmacOS Safari.**\n\nA: This is a known issue with Safari and localhost. You can work around it by using the `WithInsecureCookie` option when\ncreating a new `Passkey` instance. This will set the `Secure` flag on the session cookie to `false`.\n\n**Q: I'm getting an error \"Error validating origin\" when I try to interact with passkey.**\n\nA: This error occurs when the origin URL is not included in the `RPOrigins` list. Make sure that the origin URL is\nincluded in the `RPOrigins` list when creating a new `Passkey` instance.\n\n**Q: I'm getting an error \"WebAuthn in not supported in this browser\" when I try to interact with passkey on `localhost`\nfrom iOS Safari.**\n\nA: Mobile Safari no not store insecure cookies. To play around with the example app on iOS Safari, you should run in\nwith self-signed certificate. Use `make up` to run the example app in docker container with self-signed certificate.\n\n## Contributing\n\nBug reports, bug fixes and new features are always welcome. Please open issues and submit pull requests for any new\ncode.\n\n## License\n\nThis project is licensed under the MIT License - see\nthe [LICENSE](https://github.com/egregors/passkey/blob/main/LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegregors%2Fpasskey","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fegregors%2Fpasskey","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegregors%2Fpasskey/lists"}