Skip to content

Commit

Permalink
#27: clean up, readme update
Browse files Browse the repository at this point in the history
  • Loading branch information
egregors committed Nov 27, 2024
1 parent c696f8f commit 54ba2f2
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 61 deletions.
131 changes: 71 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,32 @@
## Table of Contents

<!-- TOC -->

* [Table of Contents](#table-of-contents)
* [Features](#features)
* [Installation](#installation)
* [Usage](#usage)
* [Table of Contents](#table-of-contents)
* [Features](#features)
* [Installation](#installation)
* [Usage](#usage)
* [Library Usage](#library-usage)
* [Implement the `UserStore` and
`SessionStore` interfaces](#implement-the-userstore-and-sessionstore-interfaces)
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
* [Client-side](#client-side)
* [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)
* [Create a new `Passkey` instance and mount the routes](#create-a-new-passkey-instance-and-mount-the-routes)
* [Client-side](#client-side)
* [Example Application](#example-application)
* [API](#api)
* [API](#api)
* [Middleware](#middleware)
* [Development](#development)
* [Development](#development)
* [Common tasks](#common-tasks)
* [Mocks](#mocks)
* [Troubleshooting](#troubleshooting)
* [FAQ](#faq)
* [Contributing](#contributing)
* [License](#license)

* [FAQ](#faq)
* [Contributing](#contributing)
* [License](#license)
<!-- TOC -->

## Features

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

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

## Usage

## Terminology

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

### Library Usage

To add a passkey service to your application, you need to do two things:
To add a passkey service to your application, you need to do a few simple steps:

#### Implement the `UserStore` and `SessionStore` interfaces
#### Implement storages: `UserStore` and two `SessionStore` one for WebAuthn and one for general session

```go
package passkey

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

type User interface {
webauthn.User
PutCredential(webauthn.Credential)
// UserStore is a persistent storage for users and credentials
type UserStore interface {
Create(username string) (User, error)
Update(User) error

Get(userID []byte) (User, error)
GetByName(username string) (User, error)
}

type UserStore interface {
GetOrCreateUser(UserID string) User
SaveUser(User)
// SessionStore is a storage for session data
type SessionStore[T webauthn.SessionData | UserSessionData] interface {
Create(data T) (string, error)
Delete(token string)

Get(token string) (*T, bool)
}

type SessionStore interface {
GenSessionID() (string, error)
GetSession(token string) (*webauthn.SessionData, bool)
SaveSession(token string, data *webauthn.SessionData)
DeleteSession(token string)
```

Your `User` model also should implement `User` interface:

```go
package main

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

// User is a user with webauthn credentials
type User interface {
webauthn.User
PutCredential(webauthn.Credential)
}

```

This interface is an extension of the `webauthn.User` interface. It adds a `PutCredential` method that allows you to
store a credential in the user object.

#### Create a new `Passkey` instance and mount the routes

The whole example is in `_example` directory.

```go
package main

import (
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
"net/url"
"os"
"time"

"github.com/egregors/passkey"
"github.com/go-webauthn/webauthn/webauthn"

"github.com/egregors/passkey"
"github.com/egregors/passkey/log"
)

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

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

storage := NewStorage()
l := log.NewLogger()

pkey, err := passkey.New(
passkey.Config{
Expand All @@ -154,12 +163,13 @@ func main() {
RPID: host, // Generally the FQDN for your site
RPOrigins: []string{origin}, // The origin URLs allowed for WebAuthn
},
UserStore: storage,
SessionStore: storage,
SessionMaxAge: 24 * time.Hour,
UserStore: NewUserStore(),
AuthSessionStore: NewSessionStore[webauthn.SessionData](),
UserSessionStore: NewSessionStore[passkey.UserSessionData](),
},
passkey.WithLogger(NewLogger()),
passkey.WithCookieMaxAge(60*time.Minute),
passkey.WithLogger(l),
passkey.WithUserSessionMaxAge(60*time.Minute),
passkey.WithSessionCookieNamePrefix("passkeyDemo"),
passkey.WithInsecureCookie(), // In order to support Safari on localhost. Do not use in production.
)
if err != nil {
Expand Down Expand Up @@ -192,8 +202,8 @@ func main() {
mux.Handle("/private", withAuth(privateMux))

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

Full list of options:

| Name | Default | Description |
|-----------------------|----------------------------------------|----------------------------------------|
| WithLogger | NullLogger | Provide custom logger |
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
| WithSessionCookieName | `sid` | Sets the name of the session cookie |
| WithCookieMaxAge | 60 minutes | Sets the max age of the session cookie |
| Name | Default | Description |
|-----------------------------|----------------------------------------|------------------------------------------------------|
| WithLogger | NullLogger | Provide custom logger |
| WithInsecureCookie | Disabled (cookie is secure by default) | Sets Cookie.Secure to false |
| WithSessionCookieNamePrefix | `pk` | Sets the name prefix of the session and user cookies |
| WithUserSessionMaxAge | 60 minutes | Sets the max age of the user session cookie |

#### Client-side

Expand All @@ -235,11 +245,12 @@ make up

## API

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

### Middleware

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

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

`passkey` contains a helper function:

Expand Down
2 changes: 1 addition & 1 deletion middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func RedirectUnauthorized(target url.URL) http.HandlerFunc {
}
}

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

0 comments on commit 54ba2f2

Please sign in to comment.