136 lines
4.1 KiB
Go
136 lines
4.1 KiB
Go
|
package oauth2
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// DefaultCookieName is the name of the stored cookie
|
||
|
DefaultCookieName = "session"
|
||
|
// DefaultInactivityDuration is the duration a token is valid without any new activity
|
||
|
DefaultInactivityDuration = 5 * time.Minute
|
||
|
)
|
||
|
|
||
|
var _ Authenticator = &cookie{}
|
||
|
|
||
|
// cookie represents the location and expiration time of new cookies.
|
||
|
type cookie struct {
|
||
|
Name string // Name is the name of the cookie stored on the browser
|
||
|
Lifespan time.Duration // Lifespan is the expiration date of the cookie. 0 means session cookie
|
||
|
Inactivity time.Duration // Inactivity is the length of time a token is valid if there is no activity
|
||
|
Now func() time.Time
|
||
|
Tokens Tokenizer
|
||
|
}
|
||
|
|
||
|
// NewCookieJWT creates an Authenticator that uses cookies for auth
|
||
|
func NewCookieJWT(secret string, lifespan time.Duration) Authenticator {
|
||
|
inactivity := DefaultInactivityDuration
|
||
|
// Server interprets a token duration longer than the cookie lifespan as
|
||
|
// a token that was issued by a server with a longer auth-duration and is
|
||
|
// thus invalid, as a security precaution. So, inactivity must be set to
|
||
|
// be less than lifespan.
|
||
|
if lifespan > 0 && inactivity > lifespan {
|
||
|
inactivity = lifespan / 2 // half of the lifespan ensures tokens can be refreshed once.
|
||
|
}
|
||
|
return &cookie{
|
||
|
Name: DefaultCookieName,
|
||
|
Lifespan: lifespan,
|
||
|
Inactivity: inactivity,
|
||
|
Now: DefaultNowTime,
|
||
|
Tokens: &JWT{
|
||
|
Secret: secret,
|
||
|
Now: DefaultNowTime,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Validate returns Principal of the Cookie if the Token is valid.
|
||
|
func (c *cookie) Validate(ctx context.Context, r *http.Request) (Principal, error) {
|
||
|
cookie, err := r.Cookie(c.Name)
|
||
|
if err != nil {
|
||
|
return Principal{}, ErrAuthentication
|
||
|
}
|
||
|
|
||
|
return c.Tokens.ValidPrincipal(ctx, Token(cookie.Value), c.Lifespan)
|
||
|
}
|
||
|
|
||
|
// Extend will extend the lifetime of the Token by the Inactivity time. Assumes
|
||
|
// Principal is already valid.
|
||
|
func (c *cookie) Extend(ctx context.Context, w http.ResponseWriter, p Principal) (Principal, error) {
|
||
|
// Refresh the token by extending its life another Inactivity duration
|
||
|
p, err := c.Tokens.ExtendedPrincipal(ctx, p, c.Inactivity)
|
||
|
if err != nil {
|
||
|
return Principal{}, ErrAuthentication
|
||
|
}
|
||
|
|
||
|
// Creating a new token with the extended principal
|
||
|
token, err := c.Tokens.Create(ctx, p)
|
||
|
if err != nil {
|
||
|
return Principal{}, ErrAuthentication
|
||
|
}
|
||
|
|
||
|
// Cookie lifespan can be indirectly figured out by taking the token's
|
||
|
// issued at time and adding the lifespan setting The token's issued at
|
||
|
// time happens to correspond to the cookie's original issued at time.
|
||
|
exp := p.IssuedAt.Add(c.Lifespan)
|
||
|
// Once the token has been extended, write it out as a new cookie.
|
||
|
c.setCookie(w, string(token), exp)
|
||
|
|
||
|
return p, nil
|
||
|
}
|
||
|
|
||
|
// Authorize will create cookies containing token information. It'll create
|
||
|
// a token with cookie.Duration of life to be stored as the cookie's value.
|
||
|
func (c *cookie) Authorize(ctx context.Context, w http.ResponseWriter, p Principal) error {
|
||
|
// Principal will be issued at Now() and will expire
|
||
|
// c.Inactivity into the future
|
||
|
now := c.Now()
|
||
|
p.IssuedAt = now
|
||
|
p.ExpiresAt = now.Add(c.Inactivity)
|
||
|
|
||
|
token, err := c.Tokens.Create(ctx, p)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// The time when the cookie expires
|
||
|
exp := now.Add(c.Lifespan)
|
||
|
c.setCookie(w, string(token), exp)
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// setCookie creates a cookie with value expiring at exp and writes it as a cookie into the response
|
||
|
func (c *cookie) setCookie(w http.ResponseWriter, value string, exp time.Time) {
|
||
|
// Cookie has a Token baked into it
|
||
|
cookie := http.Cookie{
|
||
|
Name: DefaultCookieName,
|
||
|
Value: value,
|
||
|
HttpOnly: true,
|
||
|
Path: "/",
|
||
|
}
|
||
|
|
||
|
// Only set a cookie to be persistent (endure beyond the browser session)
|
||
|
// if auth duration is greater than zero
|
||
|
if c.Lifespan > 0 {
|
||
|
cookie.Expires = exp
|
||
|
}
|
||
|
http.SetCookie(w, &cookie)
|
||
|
}
|
||
|
|
||
|
// Expire returns a cookie that will expire an existing cookie
|
||
|
func (c *cookie) Expire(w http.ResponseWriter) {
|
||
|
// to expire cookie set the time in the past
|
||
|
cookie := http.Cookie{
|
||
|
Name: DefaultCookieName,
|
||
|
Value: "none",
|
||
|
HttpOnly: true,
|
||
|
Path: "/",
|
||
|
Expires: c.Now().Add(-1 * time.Hour),
|
||
|
}
|
||
|
|
||
|
http.SetCookie(w, &cookie)
|
||
|
}
|