gomagiclink

package module
v0.9.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 29, 2025 License: MIT Imports: 12 Imported by: 0

README

Go Reference

Magic Link authentication framework for Go. The idea is to avoid asking the user for the password every time they login, and send them a "magic link" to their e-mail address. Once they confirm the login by clicking the link, the user is considered logged in. This has the advantage of not handling user passwords at all, and the disadvantage that if their e-mail account is compromised, this will cascade to services dependant on the e-mail account, such as ones implementing the magic link login.

This package implements the core part of this process, by generating a cryptographically safe magic link challenge and a session id (useful for web cookies). To keep the process safe, you need to maintain the security of the secret key, passed to the NewAuthMagicLinkController() function.

API reference

Design decisions

  • We don't write down information about the user until they verify the challenge; then we create the user record.
  • We don't write down session information at all, but verify the cookie JWT-style
  • We allow the app to optionally attach arbitrary data about a user, and store it with the user record
  • We allow easy implementation of different data stores

Workflows

See these examples for more info:

Registration / Login

  1. Construct an AuthUserDatabase - there are examples for SQL databases and a plain file system storage in this repo
  2. Construct an AuthMagicLinkController - this is the code that does crypto and login
  3. Collect user e-mail (with a web form, etc)
  4. Generate a challenge string (magic cookie) with GenerateChallenge(), construct a link with it and send it to user's e-mail
  5. Verify the challenge with VerifyChallenge(). If successful, it will return an UserAuthRecord
  6. Optionally attach custom user data to the CustomData field of the record and store the AuthUserRecord with StoreUser(). Note that this data will be stored and retrieved as JSON, so the CustomData needs to be of a type that can survive a round-trip through JSON. For example, ints will be returned as float64s.

By the nature of this login system, unique users are represented by unique e-mail addresses, but each such user also gets a UUID.

Session

After a magic link challenge has been verified, you can optionally create a session id to store in a cookie.

  1. Generate a session ID with GenerateSessionId(), send it to the browser, e.g. as a HTTP cookie, or a Bearer token
  2. Each time the browser sends back the session ID, verify it with VerifySessionId(). It will return an AuthUserRecord if successful. Inspect the CustomData field if you've set it before.

The AuthUserRecord is a structure where you can attach arbitrary information, such as information about the user's profile, or an app-specific user ID if you don't like using UUIDs that this library uses.

Sending e-mail

Configuring an e-mail server, etc. is waaaay out of scope for this package, but here's a good e-mail library for Go.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrBrokenChallenge = errors.New("broken challenge")
View Source
var ErrBrokenSessionId = errors.New("broken session id")
View Source
var ErrExpiredChallenge = errors.New("expired challenge")
View Source
var ErrExpiredSessionId = errors.New("expired session id")
View Source
var ErrInvalidChallenge = errors.New("invalid challenge")
View Source
var ErrInvalidSessionId = errors.New("invalid session id")
View Source
var ErrSecretKeyTooShort = errors.New("secret Key too short (min 16 bytes)")
View Source
var ErrUserAlreadyExists = errors.New("user already exists")
View Source
var ErrUserDisabled = errors.New("user disabled")
View Source
var ErrUserNotFound = errors.New("user not found")

Functions

func NormalizeEmail

func NormalizeEmail(email string) string

Types

type AuthMagicLinkController

type AuthMagicLinkController struct {
	// contains filtered or unexported fields
}

All functionalities needed to implement the Magic Link login system is available through the AuthMagicLinkController.

func NewAuthMagicLinkController

func NewAuthMagicLinkController(secretKey []byte, challengeExpDuration time.Duration, sessionExpDuration time.Duration, db UserAuthDatabase) (mlc *AuthMagicLinkController, err error)

NewAuthMagicLinkController configures and creates a new instance of the AuthMagicLinkController. The secretKey needs to be kept safe. To provide your own storage mechanism for the magic link data, implement the UserAuthDatabase interface. There are file system and SQL database implementations provided.

func (*AuthMagicLinkController) GenerateChallenge

func (mlc *AuthMagicLinkController) GenerateChallenge(email string) (challenge string, err error)

GenerateChallenge creates a challenge string to be used for constructing the magic link. This challenge string needs to be verified by VerifyChallenge()

func (*AuthMagicLinkController) GenerateSessionId

func (mlc *AuthMagicLinkController) GenerateSessionId(user *AuthUserRecord) (sessionId string, err error)

GenerateSessionId generates a session id suitable for using as a cookie in a web app.

func (*AuthMagicLinkController) GetUserByEmail

func (mlc *AuthMagicLinkController) GetUserByEmail(email string) (*AuthUserRecord, error)

func (*AuthMagicLinkController) GetUserCount added in v0.9.3

func (mlc *AuthMagicLinkController) GetUserCount() (int, error)

func (*AuthMagicLinkController) StoreUser

func (mlc *AuthMagicLinkController) StoreUser(user *AuthUserRecord) error

func (*AuthMagicLinkController) UserExistsByEmail

func (mlc *AuthMagicLinkController) UserExistsByEmail(email string) bool

func (*AuthMagicLinkController) UsersExist added in v0.9.4

func (mlc *AuthMagicLinkController) UsersExist() (bool, error)

func (*AuthMagicLinkController) VerifyChallenge

func (mlc *AuthMagicLinkController) VerifyChallenge(challenge string) (user *AuthUserRecord, err error)

VerifyChallenge verifies the challenge string generated by GenerateChallenge(), and returns the AuthUserRecord corresponding to the user for which the challenge was created (identifying them by their email address).

func (*AuthMagicLinkController) VerifySessionId

func (mlc *AuthMagicLinkController) VerifySessionId(sessionId string) (user *AuthUserRecord, err error)

VerifySessionId verifies the session ID generated by GenerateSessionId() and if it's valid, returns the AuthUserRecord of the associated user.

type AuthUserRecord

type AuthUserRecord struct {
	ID              uuid.UUID         `json:"id"` // Unique identifier
	Enabled         bool              `json:"enabled"`
	Email           string            `json:"email"` // Also must be unique
	AccessLevel     int               `json:"access_level"`
	FirstLoginTime  time.Time         `json:"first_login_time"`
	RecentLoginTime time.Time         `json:"recent_login_time"`
	CustomData      map[string]string `json:"custom_data"` // Apps can attach custom data to the user record
}

AuthUser represents user data

func NewAuthUserRecord

func NewAuthUserRecord(email string) (aur *AuthUserRecord, err error)

NewAuthUserRecords constructs a new AuthUserRecord. This function isn't normally directly called by the users of this package.

func (*AuthUserRecord) GetID

func (aur *AuthUserRecord) GetID() uuid.UUID

Returns the user ID.

func (*AuthUserRecord) GetKeyName

func (aur *AuthUserRecord) GetKeyName() string

Returns the Key name suitable for key-value databases.

type RecordWithID

type RecordWithID interface {
	GetID() uuid.UUID
}

type RecordWithKeyName

type RecordWithKeyName interface {
	GetKeyName() string
}

type UserAuthDatabase

type UserAuthDatabase interface {
	UserExistsByEmail(email string) bool
	StoreUser(user *AuthUserRecord) error
	GetUserById(id uuid.UUID) (*AuthUserRecord, error)
	GetUserByEmail(email string) (*AuthUserRecord, error)
	GetUserCount() (int, error) // Slow
	UsersExist() (bool, error)  // Fast
}

When a new storage provider is created, it implements this interface. See the provided storage provided in the `storage` package.

Directories

Path Synopsis
cmd
demo command
webdemo command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL