Files
hanko/backend/session/session.go
Matthew H. Irby e7a5c2df27 Feat: Add logout method to sdk (#566)
* Add endpoint to invalidate HTTP-Only cookie from the backend

* Add methods to the UserClient SDK for logout

* Remove session token fetch and add unit test for logout

* Update public router to use JWT middleware

* Add logout button to frontend. Route back to login page once logout is successful.

* Add a logout failur event

* Update logout logic in SDK

* Remove unneeded endpoint from main.go

* Update logoutlink reference

* Fix request path; undo change in package order

* Update common.css to incldue hanko-logout

* feat(fronend-sdk): remove cookie during cross-domain operations

* fix(frontend-sdk): No unauthorized error during logout, when the user is already logged out

* feat(backend): Create an audit log entry when the user logs off

* chore(frontend-sdk): re-generate jsdoc

* fix: Adjust logout response codes and the corresponding frontend sdk error handling

* chore(frontend-sdk): re-generate jsdoc

* feat: add logout endpoint specification to the docs

* Fix broken unit test

* Remove logout button from elements

* Add event listener on frontend to call the logout method from SDK

* Rollback changes to SecuredContent on e2e tests

* Update logout test on user

* Update quickstart/public/assets/css/common.css

Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com>

---------

Co-authored-by: Björn Müller <bjoern.mueller@hanko.io>
Co-authored-by: bjoern-m <56024829+bjoern-m@users.noreply.github.com>
2023-03-03 10:48:33 +01:00

129 lines
3.5 KiB
Go

package session
import (
"fmt"
"github.com/gofrs/uuid"
"github.com/lestrrat-go/jwx/v2/jwt"
"github.com/teamhanko/hanko/backend/config"
hankoJwk "github.com/teamhanko/hanko/backend/crypto/jwk"
hankoJwt "github.com/teamhanko/hanko/backend/crypto/jwt"
"net/http"
"time"
)
type Manager interface {
GenerateJWT(uuid.UUID) (string, error)
Verify(string) (jwt.Token, error)
GenerateCookie(token string) (*http.Cookie, error)
DeleteCookie() (*http.Cookie, error)
}
// Manager is used to create and verify session JWTs
type manager struct {
jwtGenerator hankoJwt.Generator
sessionLength time.Duration
cookieConfig cookieConfig
}
type cookieConfig struct {
Domain string
HttpOnly bool
SameSite http.SameSite
Secure bool
}
// NewManager returns a new Manager which will be used to create and verify sessions JWTs
func NewManager(jwkManager hankoJwk.Manager, config config.Session) (Manager, error) {
signatureKey, err := jwkManager.GetSigningKey()
if err != nil {
return nil, fmt.Errorf("failed to create session generator: %w", err)
}
verificationKeys, err := jwkManager.GetPublicKeys()
if err != nil {
return nil, fmt.Errorf("failed to create session generator: %w", err)
}
g, err := hankoJwt.NewGenerator(signatureKey, verificationKeys)
if err != nil {
return nil, fmt.Errorf("failed to create session generator: %w", err)
}
duration, _ := time.ParseDuration(config.Lifespan) // error can be ignored, value is checked in config validation
sameSite := http.SameSite(0)
switch config.Cookie.SameSite {
case "lax":
sameSite = http.SameSiteLaxMode
case "strict":
sameSite = http.SameSiteStrictMode
case "none":
sameSite = http.SameSiteNoneMode
default:
sameSite = http.SameSiteDefaultMode
}
return &manager{
jwtGenerator: g,
sessionLength: duration,
cookieConfig: cookieConfig{
Domain: config.Cookie.Domain,
HttpOnly: config.Cookie.HttpOnly,
SameSite: sameSite,
Secure: config.Cookie.Secure,
},
}, nil
}
// GenerateJWT creates a new session JWT for the given user
func (g *manager) GenerateJWT(userId uuid.UUID) (string, error) {
issuedAt := time.Now()
expiration := issuedAt.Add(g.sessionLength)
token := jwt.New()
_ = token.Set(jwt.SubjectKey, userId.String())
_ = token.Set(jwt.IssuedAtKey, issuedAt)
_ = token.Set(jwt.ExpirationKey, expiration)
//_ = token.Set(jwt.AudienceKey, []string{"http://localhost"})
signed, err := g.jwtGenerator.Sign(token)
if err != nil {
return "", err
}
return string(signed), nil
}
// Verify verifies the given JWT and returns a parsed one if verification was successful
func (g *manager) Verify(token string) (jwt.Token, error) {
parsedToken, err := g.jwtGenerator.Verify([]byte(token))
if err != nil {
return nil, fmt.Errorf("failed to verify session token: %w", err)
}
return parsedToken, nil
}
// GenerateCookie creates a new session cookie for the given user
func (g *manager) GenerateCookie(token string) (*http.Cookie, error) {
return &http.Cookie{
Name: "hanko",
Value: token,
Domain: g.cookieConfig.Domain,
Path: "/",
Secure: g.cookieConfig.Secure,
HttpOnly: g.cookieConfig.HttpOnly,
SameSite: g.cookieConfig.SameSite,
}, nil
}
// DeleteCookie returns a cookie that will expire the cookie on the frontend
func (g *manager) DeleteCookie() (*http.Cookie, error) {
return &http.Cookie{
Name: "hanko",
Value: "",
Domain: g.cookieConfig.Domain,
Path: "/",
Secure: g.cookieConfig.Secure,
HttpOnly: g.cookieConfig.HttpOnly,
SameSite: g.cookieConfig.SameSite,
MaxAge: -1,
}, nil
}