vednor with auth 0.2.1 to minimize multiple token refreshes
This commit is contained in:
+6
-4
@@ -53,8 +53,9 @@ type Opts struct {
|
||||
AvatarResizeLimit int // resize avatar's limit in pixels
|
||||
AvatarRoutePath string // avatar routing prefix, i.e. "/api/v1/avatar", default `/avatar`
|
||||
|
||||
AdminPasswd string // if presented, allows basic auth with user admin and given password
|
||||
Logger logger.L // logger interface, default is no logging at all
|
||||
AdminPasswd string // if presented, allows basic auth with user admin and given password
|
||||
RefreshFactor int // estimated number of request client sends in parallel during token refresh.
|
||||
Logger logger.L // logger interface, default is no logging at all
|
||||
}
|
||||
|
||||
// NewService initializes everything
|
||||
@@ -64,8 +65,9 @@ func NewService(opts Opts) (res *Service) {
|
||||
opts: opts,
|
||||
logger: opts.Logger,
|
||||
authMiddleware: middleware.Authenticator{
|
||||
Validator: opts.Validator,
|
||||
AdminPasswd: opts.AdminPasswd,
|
||||
Validator: opts.Validator,
|
||||
AdminPasswd: opts.AdminPasswd,
|
||||
RefreshFactor: opts.RefreshFactor,
|
||||
},
|
||||
issuer: opts.Issuer,
|
||||
}
|
||||
|
||||
+22
-6
@@ -2,6 +2,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -14,10 +15,11 @@ import (
|
||||
// Authenticator is top level auth object providing middlewares
|
||||
type Authenticator struct {
|
||||
logger.L
|
||||
JWTService TokenService
|
||||
Providers []provider.Service
|
||||
Validator token.Validator
|
||||
AdminPasswd string
|
||||
JWTService TokenService
|
||||
Providers []provider.Service
|
||||
Validator token.Validator
|
||||
AdminPasswd string
|
||||
RefreshFactor int
|
||||
}
|
||||
|
||||
// TokenService defines interface accessing tokens
|
||||
@@ -96,7 +98,7 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
if a.JWTService.IsExpired(claims) {
|
||||
if a.shouldRefresh(claims) {
|
||||
if claims, err = a.refreshExpiredToken(w, claims); err != nil {
|
||||
a.JWTService.Reset(w)
|
||||
onError(h, w, r, errors.Wrap(err, "can't refresh token"))
|
||||
@@ -117,7 +119,6 @@ func (a *Authenticator) auth(reqAuth bool) func(http.Handler) http.Handler {
|
||||
|
||||
// refreshExpiredToken makes a new token with passed claims
|
||||
func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token.Claims) (token.Claims, error) {
|
||||
|
||||
claims.ExpiresAt = 0 // this will cause now+duration for refreshed token
|
||||
if err := a.JWTService.Set(w, claims); err != nil {
|
||||
return token.Claims{}, err
|
||||
@@ -125,6 +126,21 @@ func (a *Authenticator) refreshExpiredToken(w http.ResponseWriter, claims token.
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// shouldRefresh checks if token expired with an optional random rejection of refresh.
|
||||
// the goal is to prevent multiple refresh request executed at the same time by allowing only some of them
|
||||
func (a *Authenticator) shouldRefresh(claims token.Claims) bool {
|
||||
if !a.JWTService.IsExpired(claims) {
|
||||
return false
|
||||
}
|
||||
|
||||
// disable randomizing with 0 factor
|
||||
if a.RefreshFactor == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return rand.Int31n(int32(a.RefreshFactor)) == 0 // randomize selection
|
||||
}
|
||||
|
||||
// AdminOnly middleware allows access for admins only
|
||||
// this handler internally wrapped with auth(true) to avoid situation if AdminOnly defined without prior Auth
|
||||
func (a *Authenticator) AdminOnly(next http.Handler) http.Handler {
|
||||
|
||||
+6
@@ -130,6 +130,12 @@ func (d *DevAuthServer) Run(ctx context.Context) {
|
||||
}
|
||||
d.lock.Unlock()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
d.Logf("[DEBUG] cancellation via context, %v", ctx.Err())
|
||||
d.Shutdown()
|
||||
}()
|
||||
|
||||
err = d.httpServer.ListenAndServe()
|
||||
d.Logf("[WARN] dev oauth2 server terminated, %s", err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/go-pkgz/rest"
|
||||
|
||||
"github.com/go-pkgz/auth/logger"
|
||||
@@ -72,7 +72,7 @@ func (p DirectHandler) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rest.RenderJSON(w, r, claims.User)
|
||||
}
|
||||
|
||||
// AuthHandler doesn't do anyting for direct login as it has no callbacks
|
||||
// AuthHandler doesn't do anything for direct login as it has no callbacks
|
||||
func (p DirectHandler) AuthHandler(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
// LogoutHandler - GET /logout
|
||||
|
||||
+3
@@ -122,6 +122,9 @@ func (j *Service) Parse(tokenString string) (Claims, error) {
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "can't pre-parse token")
|
||||
}
|
||||
if _, ok := preToken.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return "", errors.Errorf("unexpected signing method: %v", preToken.Header["alg"])
|
||||
}
|
||||
preClaims, ok := preToken.Claims.(*Claims)
|
||||
if !ok {
|
||||
return "", errors.New("invalid token")
|
||||
|
||||
Reference in New Issue
Block a user