Skip to content

Commit 54ba2f2

Browse files
committed
#27: clean up, readme update
1 parent c696f8f commit 54ba2f2

File tree

2 files changed

+72
-61
lines changed

2 files changed

+72
-61
lines changed

README.md

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,32 @@
1515
## Table of Contents
1616

1717
<!-- TOC -->
18-
19-
* [Table of Contents](#table-of-contents)
20-
* [Features](#features)
21-
* [Installation](#installation)
22-
* [Usage](#usage)
18+
* [Table of Contents](#table-of-contents)
19+
* [Features](#features)
20+
* [Installation](#installation)
21+
* [Usage](#usage)
2322
* [Library Usage](#library-usage)
24-
* [Implement the `UserStore` and
25-
`SessionStore` interfaces](#implement-the-userstore-and-sessionstore-interfaces)
26-
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
27-
* [Client-side](#client-side)
23+
* [Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session](#implement-storages-userstore-and-two-sessionstore-one-for-webauthn-and-one-for-general-session)
24+
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
25+
* [Client-side](#client-side)
2826
* [Example Application](#example-application)
29-
* [API](#api)
27+
* [API](#api)
3028
* [Middleware](#middleware)
31-
* [Development](#development)
29+
* [Development](#development)
3230
* [Common tasks](#common-tasks)
3331
* [Mocks](#mocks)
3432
* [Troubleshooting](#troubleshooting)
35-
* [FAQ](#faq)
36-
* [Contributing](#contributing)
37-
* [License](#license)
38-
33+
* [FAQ](#faq)
34+
* [Contributing](#contributing)
35+
* [License](#license)
3936
<!-- TOC -->
4037

4138
## Features
4239

4340
- **User Management**: Handle user information and credentials.
4441
- **WebAuthn Integration**: Easily integrate with WebAuthn for authentication.
4542
- **Session Management**: Manage user sessions securely.
46-
- **Middleware Support**: Implement middleware for authenticated routes.
43+
- **Middleware Support**: Middleware for authenticated routes.
4744

4845
> [!WARNING]
4946
> Stable version is not released yet. The API and the lib are under development.
@@ -74,61 +71,73 @@ go get github.com/egregors/passkey
7471

7572
## Usage
7673

77-
## Terminology
78-
79-
- **WebAuthn Credential ID** – is a unique ID generated by the authenticator (e.g. your smartphone, laptop or hardware
80-
security key like YubiKey) during the registration (sign-up) process of a new credential (passkey).
81-
- **WebAuthn User ID (user.id)** – is an ID specified by the relying party (RP) to represent a user account within their
82-
system. In the context of passkeys, the User ID (user.id) plays a crucial role in linking a particular user with their
83-
credentials (passkeys).
84-
8574
### Library Usage
8675

87-
To add a passkey service to your application, you need to do two things:
76+
To add a passkey service to your application, you need to do a few simple steps:
8877

89-
#### Implement the `UserStore` and `SessionStore` interfaces
78+
#### Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session
9079

9180
```go
9281
package passkey
9382

9483
import "github.com/go-webauthn/webauthn/webauthn"
9584

96-
type User interface {
97-
webauthn.User
98-
PutCredential(webauthn.Credential)
85+
// UserStore is a persistent storage for users and credentials
86+
type UserStore interface {
87+
Create(username string) (User, error)
88+
Update(User) error
89+
90+
Get(userID []byte) (User, error)
91+
GetByName(username string) (User, error)
9992
}
10093

101-
type UserStore interface {
102-
GetOrCreateUser(UserID string) User
103-
SaveUser(User)
94+
// SessionStore is a storage for session data
95+
type SessionStore[T webauthn.SessionData | UserSessionData] interface {
96+
Create(data T) (string, error)
97+
Delete(token string)
98+
99+
Get(token string) (*T, bool)
104100
}
105101

106-
type SessionStore interface {
107-
GenSessionID() (string, error)
108-
GetSession(token string) (*webauthn.SessionData, bool)
109-
SaveSession(token string, data *webauthn.SessionData)
110-
DeleteSession(token string)
102+
```
103+
104+
Your `User` model also should implement `User` interface:
105+
106+
```go
107+
package main
108+
109+
import "github.com/go-webauthn/webauthn/webauthn"
110+
111+
// User is a user with webauthn credentials
112+
type User interface {
113+
webauthn.User
114+
PutCredential(webauthn.Credential)
111115
}
112116

113117
```
114118

119+
This interface is an extension of the `webauthn.User` interface. It adds a `PutCredential` method that allows you to
120+
store a credential in the user object.
121+
115122
#### Create a new `Passkey` instance and mount the routes
116123

124+
The whole example is in `_example` directory.
125+
117126
```go
118127
package main
119128

120129
import (
121130
"embed"
122131
"fmt"
123-
"html/template"
124132
"io/fs"
125133
"net/http"
126134
"net/url"
127-
"os"
128135
"time"
129136

130-
"github.com/egregors/passkey"
131137
"github.com/go-webauthn/webauthn/webauthn"
138+
139+
"github.com/egregors/passkey"
140+
"github.com/egregors/passkey/log"
132141
)
133142

134143
//go:embed web/*
@@ -145,7 +154,7 @@ func main() {
145154

146155
origin := fmt.Sprintf("%s://%s%s%s", proto, sub, host, originPort)
147156

148-
storage := NewStorage()
157+
l := log.NewLogger()
149158

150159
pkey, err := passkey.New(
151160
passkey.Config{
@@ -154,12 +163,13 @@ func main() {
154163
RPID: host, // Generally the FQDN for your site
155164
RPOrigins: []string{origin}, // The origin URLs allowed for WebAuthn
156165
},
157-
UserStore: storage,
158-
SessionStore: storage,
159-
SessionMaxAge: 24 * time.Hour,
166+
UserStore: NewUserStore(),
167+
AuthSessionStore: NewSessionStore[webauthn.SessionData](),
168+
UserSessionStore: NewSessionStore[passkey.UserSessionData](),
160169
},
161-
passkey.WithLogger(NewLogger()),
162-
passkey.WithCookieMaxAge(60*time.Minute),
170+
passkey.WithLogger(l),
171+
passkey.WithUserSessionMaxAge(60*time.Minute),
172+
passkey.WithSessionCookieNamePrefix("passkeyDemo"),
163173
passkey.WithInsecureCookie(), // In order to support Safari on localhost. Do not use in production.
164174
)
165175
if err != nil {
@@ -192,8 +202,8 @@ func main() {
192202
mux.Handle("/private", withAuth(privateMux))
193203

194204
// start the server
195-
fmt.Printf("Listening on %s\n", origin)
196-
if err := http.ListenAndServe(serverPort, mux); err != nil {
205+
l.Infof("Listening on %s\n", origin)
206+
if err := http.ListenAndServe(serverPort, mux); err != nil { //nolint:gosec
197207
panic(err)
198208
}
199209
}
@@ -204,12 +214,12 @@ You can optionally provide a logger to the `New` function using the `WithLogger`
204214

205215
Full list of options:
206216

207-
| Name | Default | Description |
208-
|-----------------------|----------------------------------------|----------------------------------------|
209-
| WithLogger | NullLogger | Provide custom logger |
210-
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
211-
| WithSessionCookieName | `sid` | Sets the name of the session cookie |
212-
| WithCookieMaxAge | 60 minutes | Sets the max age of the session cookie |
217+
| Name | Default | Description |
218+
|-----------------------------|----------------------------------------|------------------------------------------------------|
219+
| WithLogger | NullLogger | Provide custom logger |
220+
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
221+
| WithSessionCookieNamePrefix | `pk` | Sets the name prefix of the session and user cookies |
222+
| WithUserSessionMaxAge | 60 minutes | Sets the max age of the user session cookie |
213223

214224
#### Client-side
215225

@@ -235,11 +245,12 @@ make up
235245

236246
## API
237247

238-
| Method | Description |
239-
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
240-
| `New(cfg Config, opts ...Option) (*Passkey, error)` | Creates a new Passkey instance. |
241-
| `MountRoutes(mux *http.ServeMux, path string)` | Mounts the Passkey routes onto a given HTTP multiplexer. |
242-
| `Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler` | Middleware to protect routes that require authentication. |
248+
| Method | Description |
249+
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
250+
| `New(cfg Config, opts ...Option) (*Passkey, error)` | Creates a new Passkey instance. |
251+
| `MountRoutes(mux *http.ServeMux, path string)` | Mounts the Passkey routes onto a given HTTP multiplexer. |
252+
| `Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func(next http.Handler) http.Handler` | Middleware to protect routes that require authentication. |
253+
| `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. |
243254

244255
### Middleware
245256

@@ -251,7 +262,7 @@ Auth(userIDKey string, onSuccess, onFail http.HandlerFunc) func (next http.Handl
251262

252263
It takes key for context and two callback functions that are called when the user is authenticated or not.
253264
You can use the context key to retrieve the authenticated userID from the request context
254-
with `passkey.UserFromContext`.
265+
with `passkey.UserIDFromCtx`.
255266

256267
`passkey` contains a helper function:
257268

middleware.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func RedirectUnauthorized(target url.URL) http.HandlerFunc {
6565
}
6666
}
6767

68-
// UserIDFromCtx returns the user ID from the request context. If the userID is not found, it returns an empty string.
68+
// UserIDFromCtx returns the user ID from the request context. If the userID is not found, it returns nil and false.
6969
func UserIDFromCtx(ctx context.Context, pkUserKey string) ([]byte, bool) {
7070
if ctx.Value(pkUserKey) == nil {
7171
return nil, false

0 commit comments

Comments
 (0)