Merge pull request #191 from authorizerdev/fix/session-invalidation

fix: session invalidation
This commit is contained in:
Lakhan Samani 2022-06-12 09:08:57 +05:30 committed by GitHub
commit 3337dbd0a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 493 additions and 324 deletions

View File

@ -7,4 +7,6 @@ const (
TokenTypeAccessToken = "access_token" TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type // TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token" TokenTypeIdentityToken = "id_token"
// TokenTypeSessionToken is the session_token type used for browser session
TokenTypeSessionToken = "session_token"
) )

View File

@ -38,7 +38,6 @@ func (user *User) AsAPIUser() *model.User {
email := user.Email email := user.Email
createdAt := user.CreatedAt createdAt := user.CreatedAt
updatedAt := user.UpdatedAt updatedAt := user.UpdatedAt
revokedTimestamp := user.RevokedTimestamp
return &model.User{ return &model.User{
ID: user.ID, ID: user.ID,
Email: user.Email, Email: user.Email,
@ -55,7 +54,7 @@ func (user *User) AsAPIUser() *model.User {
PhoneNumberVerified: &isPhoneVerified, PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture, Picture: user.Picture,
Roles: strings.Split(user.Roles, ","), Roles: strings.Split(user.Roles, ","),
RevokedTimestamp: revokedTimestamp, RevokedTimestamp: user.RevokedTimestamp,
CreatedAt: &createdAt, CreatedAt: &createdAt,
UpdatedAt: &updatedAt, UpdatedAt: &updatedAt,
} }

View File

@ -68,7 +68,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
opts.SetSort(bson.M{"created_at": -1}) opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination paginationClone := pagination
// TODO add pagination total
userCollection := p.db.Collection(models.Collections.User, options.Collection()) userCollection := p.db.Collection(models.Collections.User, options.Collection())
count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count()) count, err := userCollection.CountDocuments(nil, bson.M{}, options.Count())

View File

@ -97,7 +97,6 @@ func (p *provider) ListUsers(pagination model.Pagination) (*model.Users, error)
func (p *provider) GetUserByEmail(email string) (models.User, error) { func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("email = ?", email).First(&user) result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }
@ -110,7 +109,6 @@ func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User var user models.User
result := p.db.Where("id = ?", id).First(&user) result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil { if result.Error != nil {
return user, result.Error return user, result.Error
} }

View File

@ -222,10 +222,7 @@ func AuthorizeHandler() gin.HandlerFunc {
// based on the response type, generate the response // based on the response type, generate the response
if isResponseTypeCode { if isResponseTypeCode {
// rollover the session for security // rollover the session for security
err = memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
if err != nil {
log.Debug("Failed to remove state: ", err)
}
nonce := uuid.New().String() nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope) newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil { if err != nil {
@ -246,7 +243,7 @@ func AuthorizeHandler() gin.HandlerFunc {
return return
} }
memorystore.Provider.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
cookie.SetSession(gc, newSessionToken) cookie.SetSession(gc, newSessionToken)
code := uuid.New().String() code := uuid.New().String()
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken) memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
@ -283,9 +280,9 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
return return
} }
memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(user.ID, claims.Nonce)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@ -308,7 +305,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token params += "&refresh_token=" + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
if isQuery { if isQuery {

View File

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"encoding/json"
"net/http" "net/http"
"strings" "strings"
@ -10,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Handler to logout user // Handler to logout user
@ -35,12 +37,17 @@ func LogoutHandler() gin.HandlerFunc {
return return
} }
fingerPrint := string(decryptedFingerPrint) var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
err = memorystore.Provider.RemoveState(fingerPrint)
if err != nil { if err != nil {
log.Debug("Failed to remove state: ", err) log.Debug("Failed to decrypt fingerprint: ", err)
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
} }
memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc) cookie.DeleteSession(gc)
if redirectURL != "" { if redirectURL != "" {

View File

@ -36,7 +36,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
log.Debug("Invalid oauth state: ", state) log.Debug("Invalid oauth state: ", state)
c.JSON(400, gin.H{"error": "invalid oauth state"}) c.JSON(400, gin.H{"error": "invalid oauth state"})
} }
memorystore.Provider.GetState(state)
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "___")
@ -46,6 +45,9 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return return
} }
// remove state from store
go memorystore.Provider.RemoveState(state)
stateValue := sessionSplit[0] stateValue := sessionSplit[0]
redirectURL := sessionSplit[1] redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",") inputRoles := strings.Split(sessionSplit[2], ",")
@ -117,9 +119,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user) user, _ = db.Provider.AddUser(user)
} else { } else {
user = existingUser
if user.RevokedTimestamp != nil { if user.RevokedTimestamp != nil {
log.Debug("User access revoked at: ", user.RevokedTimestamp) log.Debug("User access revoked at: ", user.RevokedTimestamp)
c.JSON(400, gin.H{"error": "user access has been revoked"}) c.JSON(400, gin.H{"error": "user access has been revoked"})
return
} }
// user exists in db, check if method was google // user exists in db, check if method was google
@ -128,7 +132,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider signupMethod = signupMethod + "," + provider
} }
user = existingUser
user.SignupMethods = signupMethod user.SignupMethods = signupMethod
if user.EmailVerifiedAt == nil { if user.EmailVerifiedAt == nil {
@ -200,12 +203,12 @@ func OAuthCallbackHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=` + authToken.RefreshToken.Token params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{

View File

@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
) )
// Revoke handler to revoke refresh token // Revoke handler to revoke refresh token
@ -45,7 +46,17 @@ func RevokeHandler() gin.HandlerFunc {
return return
} }
memorystore.Provider.RemoveState(refreshToken) claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
log.Debug("Client ID is invalid: ", clientID)
gc.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
"error_description": "Failed to parse jwt",
})
return
}
memorystore.Provider.DeleteUserSession(claims["sub"].(string), claims["nonce"].(string))
gc.JSON(http.StatusOK, gin.H{ gc.JSON(http.StatusOK, gin.H{
"message": "Token revoked successfully", "message": "Token revoked successfully",

View File

@ -107,6 +107,7 @@ func TokenHandler() gin.HandlerFunc {
return return
} }
go memorystore.Provider.RemoveState(encryptedCode)
// split session data // split session data
// it contains code@sessiontoken // it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@") sessionDataSplit := strings.Split(sessionData, "@")
@ -130,11 +131,11 @@ func TokenHandler() gin.HandlerFunc {
}) })
return return
} }
// rollover the session for security
memorystore.Provider.RemoveState(sessionDataSplit[1])
userID = claims.Subject userID = claims.Subject
roles = claims.Roles roles = claims.Roles
scope = claims.Scope scope = claims.Scope
// rollover the session for security
go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
} else { } else {
// validate refresh token // validate refresh token
if refreshToken == "" { if refreshToken == "" {
@ -163,7 +164,7 @@ func TokenHandler() gin.HandlerFunc {
scope = append(scope, v.(string)) scope = append(scope, v.(string))
} }
// remove older refresh token and rotate it for security // remove older refresh token and rotate it for security
memorystore.Provider.RemoveState(refreshToken) go memorystore.Provider.DeleteUserSession(userID, claims["nonce"].(string))
} }
user, err := db.Provider.GetUserByID(userID) user, err := db.Provider.GetUserByID(userID)
@ -185,8 +186,8 @@ func TokenHandler() gin.HandlerFunc {
}) })
return return
} }
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
@ -204,7 +205,7 @@ func TokenHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token res["refresh_token"] = authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
gc.JSON(http.StatusOK, res) gc.JSON(http.StatusOK, res)

View File

@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -42,7 +43,7 @@ func VerifyEmailHandler() gin.HandlerFunc {
// verify if token exists in db // verify if token exists in db
hostname := parsers.GetHost(c) hostname := parsers.GetHost(c)
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(tokenInQuery)
if err != nil { if err != nil {
log.Debug("Error parsing token: ", err) log.Debug("Error parsing token: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@ -50,7 +51,14 @@ func VerifyEmailHandler() gin.HandlerFunc {
return return
} }
user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Error validating jwt claims: ", err)
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
user, err := db.Provider.GetUserByEmail(verificationRequest.Email)
if err != nil { if err != nil {
log.Debug("Error getting user: ", err) log.Debug("Error getting user: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@ -100,12 +108,12 @@ func VerifyEmailHandler() gin.HandlerFunc {
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash) cookie.SetSession(c, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}` params = params + `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
if redirectURL == "" { if redirectURL == "" {

View File

@ -2,24 +2,23 @@ package inmemory
import ( import (
"sync" "sync"
"github.com/authorizerdev/authorizer/server/memorystore/providers/inmemory/stores"
) )
type provider struct { type provider struct {
mutex sync.Mutex mutex sync.Mutex
sessionStore map[string]map[string]string sessionStore *stores.SessionStore
stateStore map[string]string stateStore *stores.StateStore
envStore *EnvStore envStore *stores.EnvStore
} }
// NewInMemoryStore returns a new in-memory store. // NewInMemoryStore returns a new in-memory store.
func NewInMemoryProvider() (*provider, error) { func NewInMemoryProvider() (*provider, error) {
return &provider{ return &provider{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
sessionStore: map[string]map[string]string{}, envStore: stores.NewEnvStore(),
stateStore: map[string]string{}, sessionStore: stores.NewSessionStore(),
envStore: &EnvStore{ stateStore: stores.NewStateStore(),
mutex: sync.Mutex{},
store: map[string]interface{}{},
},
}, nil }, nil
} }

View File

@ -3,46 +3,44 @@ package inmemory
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
) )
// ClearStore clears the in-memory store. // SetUserSession sets the user session
func (c *provider) ClearStore() error { func (c *provider) SetUserSession(userId, key, token string) error {
if os.Getenv("ENV") != constants.TestEnv { c.sessionStore.Set(userId, key, token)
c.mutex.Lock()
defer c.mutex.Unlock()
}
c.sessionStore = map[string]map[string]string{}
return nil return nil
} }
// GetUserSessions returns all the user session token from the in-memory store. // GetAllUserSessions returns all the user sessions token from the in-memory store.
func (c *provider) GetUserSessions(userId string) map[string]string { func (c *provider) GetAllUserSessions(userId string) (map[string]string, error) {
res := map[string]string{} data := c.sessionStore.GetAll(userId)
for k, v := range c.stateStore { return data, nil
split := strings.Split(v, "@")
if split[1] == userId {
res[k] = split[0]
}
} }
return res // GetUserSession returns value for given session token
func (c *provider) GetUserSession(userId, sessionToken string) (string, error) {
return c.sessionStore.Get(userId, sessionToken), nil
} }
// DeleteAllUserSession deletes all the user sessions from in-memory store. // DeleteAllUserSessions deletes all the user sessions from in-memory store.
func (c *provider) DeleteAllUserSession(userId string) error { func (c *provider) DeleteAllUserSessions(userId string) error {
if os.Getenv("ENV") != constants.TestEnv { if os.Getenv("ENV") != constants.TestEnv {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
} }
sessions := c.GetUserSessions(userId) c.sessionStore.RemoveAll(userId)
for k := range sessions { return nil
c.RemoveState(k)
} }
// DeleteUserSession deletes the user session from the in-memory store.
func (c *provider) DeleteUserSession(userId, sessionToken string) error {
if os.Getenv("ENV") != constants.TestEnv {
c.mutex.Lock()
defer c.mutex.Unlock()
}
c.sessionStore.Remove(userId, sessionToken)
return nil return nil
} }
@ -52,29 +50,19 @@ func (c *provider) SetState(key, state string) error {
c.mutex.Lock() c.mutex.Lock()
defer c.mutex.Unlock() defer c.mutex.Unlock()
} }
c.stateStore[key] = state c.stateStore.Set(key, state)
return nil return nil
} }
// GetState gets the state from the in-memory store. // GetState gets the state from the in-memory store.
func (c *provider) GetState(key string) (string, error) { func (c *provider) GetState(key string) (string, error) {
state := "" return c.stateStore.Get(key), nil
if stateVal, ok := c.stateStore[key]; ok {
state = stateVal
}
return state, nil
} }
// RemoveState removes the state from the in-memory store. // RemoveState removes the state from the in-memory store.
func (c *provider) RemoveState(key string) error { func (c *provider) RemoveState(key string) error {
if os.Getenv("ENV") != constants.TestEnv { c.stateStore.Remove(key)
c.mutex.Lock()
defer c.mutex.Unlock()
}
delete(c.stateStore, key)
return nil return nil
} }

View File

@ -1,4 +1,4 @@
package inmemory package stores
import ( import (
"os" "os"
@ -13,6 +13,14 @@ type EnvStore struct {
store map[string]interface{} store map[string]interface{}
} }
// NewEnvStore create a new env store
func NewEnvStore() *EnvStore {
return &EnvStore{
mutex: sync.Mutex{},
store: make(map[string]interface{}),
}
}
// UpdateEnvStore to update the whole env store object // UpdateEnvStore to update the whole env store object
func (e *EnvStore) UpdateStore(store map[string]interface{}) { func (e *EnvStore) UpdateStore(store map[string]interface{}) {
if os.Getenv("ENV") != constants.TestEnv { if os.Getenv("ENV") != constants.TestEnv {

View File

@ -0,0 +1,67 @@
package stores
import (
"os"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// SessionStore struct to store the env variables
type SessionStore struct {
mutex sync.Mutex
store map[string]map[string]string
}
// NewSessionStore create a new session store
func NewSessionStore() *SessionStore {
return &SessionStore{
mutex: sync.Mutex{},
store: make(map[string]map[string]string),
}
}
// Get returns the value of the key in state store
func (s *SessionStore) Get(key, subKey string) string {
return s.store[key][subKey]
}
// Set sets the value of the key in state store
func (s *SessionStore) Set(key string, subKey, value string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
s.store[key][subKey] = value
}
// RemoveAll all values for given key
func (s *SessionStore) RemoveAll(key string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
delete(s.store, key)
}
// Remove value for given key and subkey
func (s *SessionStore) Remove(key, subKey string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
if _, ok := s.store[key]; ok {
delete(s.store[key], subKey)
}
}
// Get all the values for given key
func (s *SessionStore) GetAll(key string) map[string]string {
if _, ok := s.store[key]; !ok {
s.store[key] = make(map[string]string)
}
return s.store[key]
}

View File

@ -0,0 +1,46 @@
package stores
import (
"os"
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
// StateStore struct to store the env variables
type StateStore struct {
mutex sync.Mutex
store map[string]string
}
// NewStateStore create a new state store
func NewStateStore() *StateStore {
return &StateStore{
mutex: sync.Mutex{},
store: make(map[string]string),
}
}
// Get returns the value of the key in state store
func (s *StateStore) Get(key string) string {
return s.store[key]
}
// Set sets the value of the key in state store
func (s *StateStore) Set(key string, value string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
s.store[key] = value
}
// Remove removes the key from state store
func (s *StateStore) Remove(key string) {
if os.Getenv("ENV") != constants.TestEnv {
s.mutex.Lock()
defer s.mutex.Unlock()
}
delete(s.store, key)
}

View File

@ -2,12 +2,17 @@ package providers
// Provider defines current memory store provider // Provider defines current memory store provider
type Provider interface { type Provider interface {
// SetUserSession sets the user session
SetUserSession(userId, key, token string) error
// GetAllUserSessions returns all the user sessions from the session store
GetAllUserSessions(userId string) (map[string]string, error)
// GetUserSession returns the session token for given token
GetUserSession(userId, key string) (string, error)
// DeleteUserSession deletes the user session
DeleteUserSession(userId, key string) error
// DeleteAllSessions deletes all the sessions from the session store // DeleteAllSessions deletes all the sessions from the session store
DeleteAllUserSession(userId string) error DeleteAllUserSessions(userId string) error
// GetUserSessions returns all the user sessions from the session store
GetUserSessions(userId string) map[string]string
// ClearStore clears the session store for authorizer tokens
ClearStore() error
// SetState sets the login state (key, value form) in the session store // SetState sets the login state (key, value form) in the session store
SetState(key, state string) error SetState(key, state string) error
// GetState returns the state from the session store // GetState returns the state from the session store

View File

@ -2,67 +2,78 @@ package redis
import ( import (
"strconv" "strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
var ( var (
// session store prefix // state store prefix
sessionStorePrefix = "authorizer_session:" stateStorePrefix = "authorizer_state:"
// env store prefix // env store prefix
envStorePrefix = "authorizer_env" envStorePrefix = "authorizer_env"
) )
// ClearStore clears the redis store for authorizer related tokens // SetUserSession sets the user session in redis store.
func (c *provider) ClearStore() error { func (c *provider) SetUserSession(userId, key, token string) error {
err := c.store.Del(c.ctx, sessionStorePrefix+"*").Err() err := c.store.HSet(c.ctx, userId, key, token).Err()
if err != nil { if err != nil {
log.Debug("Error clearing redis store: ", err) log.Debug("Error saving to redis: ", err)
return err return err
} }
return nil return nil
} }
// GetUserSessions returns all the user session token from the redis store. // GetAllUserSessions returns all the user session token from the redis store.
func (c *provider) GetUserSessions(userID string) map[string]string { func (c *provider) GetAllUserSessions(userID string) (map[string]string, error) {
data, err := c.store.HGetAll(c.ctx, "*").Result() data, err := c.store.HGetAll(c.ctx, userID).Result()
if err != nil { if err != nil {
log.Debug("error getting token from redis store: ", err) log.Debug("error getting all user sessions from redis store: ", err)
return nil, err
} }
res := map[string]string{} return data, nil
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
} }
return res // GetUserSession returns the user session from redis store.
} func (c *provider) GetUserSession(userId, key string) (string, error) {
data, err := c.store.HGet(c.ctx, userId, key).Result()
// DeleteAllUserSession deletes all the user session from redis
func (c *provider) DeleteAllUserSession(userId string) error {
sessions := c.GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v).Err()
if err != nil { if err != nil {
log.Debug("Error deleting redis token: ", err) return "", err
}
return data, nil
}
// DeleteUserSession deletes the user session from redis store.
func (c *provider) DeleteUserSession(userId, key string) error {
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeSessionToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err return err
} }
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeAccessToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
} }
if err := c.store.HDel(c.ctx, userId, constants.TokenTypeRefreshToken+"_"+key).Err(); err != nil {
log.Debug("Error deleting user session from redis: ", err)
return err
}
return nil
} }
// DeleteAllUserSessions deletes all the user session from redis
func (c *provider) DeleteAllUserSessions(userID string) error {
err := c.store.Del(c.ctx, userID).Err()
if err != nil {
log.Debug("Error deleting all user sessions from redis: ", err)
return err
}
return nil return nil
} }
// SetState sets the state in redis store. // SetState sets the state in redis store.
func (c *provider) SetState(key, value string) error { func (c *provider) SetState(key, value string) error {
err := c.store.Set(c.ctx, sessionStorePrefix+key, value, 0).Err() err := c.store.Set(c.ctx, stateStorePrefix+key, value, 0).Err()
if err != nil { if err != nil {
log.Debug("Error saving redis token: ", err) log.Debug("Error saving redis token: ", err)
return err return err
@ -73,18 +84,18 @@ func (c *provider) SetState(key, value string) error {
// GetState gets the state from redis store. // GetState gets the state from redis store.
func (c *provider) GetState(key string) (string, error) { func (c *provider) GetState(key string) (string, error) {
var res string data, err := c.store.Get(c.ctx, stateStorePrefix+key).Result()
err := c.store.Get(c.ctx, sessionStorePrefix+key).Scan(&res)
if err != nil { if err != nil {
log.Debug("error getting token from redis store: ", err) log.Debug("error getting token from redis store: ", err)
return "", err
} }
return res, err return data, err
} }
// RemoveState removes the state from redis store. // RemoveState removes the state from redis store.
func (c *provider) RemoveState(key string) error { func (c *provider) RemoveState(key string) error {
err := c.store.Del(c.ctx, sessionStorePrefix+key).Err() err := c.store.Del(c.ctx, stateStorePrefix+key).Err()
if err != nil { if err != nil {
log.Fatalln("Error deleting redis token: ", err) log.Fatalln("Error deleting redis token: ", err)
return err return err
@ -137,22 +148,20 @@ func (c *provider) UpdateEnvVariable(key string, value interface{}) error {
// GetStringStoreEnvVariable to get the string env variable from env store // GetStringStoreEnvVariable to get the string env variable from env store
func (c *provider) GetStringStoreEnvVariable(key string) (string, error) { func (c *provider) GetStringStoreEnvVariable(key string) (string, error) {
var res string data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(&res)
if err != nil { if err != nil {
return "", nil return "", nil
} }
return res, nil return data, nil
} }
// GetBoolStoreEnvVariable to get the bool env variable from env store // GetBoolStoreEnvVariable to get the bool env variable from env store
func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) { func (c *provider) GetBoolStoreEnvVariable(key string) (bool, error) {
var res bool data, err := c.store.HGet(c.ctx, envStorePrefix, key).Result()
err := c.store.HGet(c.ctx, envStorePrefix, key).Scan(res)
if err != nil { if err != nil {
return false, nil return false, nil
} }
return res, nil return data == "1", nil
} }

View File

@ -38,7 +38,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
return res, err return res, err
} }
go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) go memorystore.Provider.DeleteAllUserSessions(user.ID)
err = db.Provider.DeleteUser(user) err = db.Provider.DeleteUser(user)
if err != nil { if err != nil {

View File

@ -117,12 +117,12 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
} }
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{

View File

@ -2,6 +2,7 @@ package resolvers
import ( import (
"context" "context"
"encoding/json"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -9,38 +10,41 @@ import (
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
// LogoutResolver is a resolver for logout mutation // LogoutResolver is a resolver for logout mutation
func LogoutResolver(ctx context.Context) (*model.Response, error) { func LogoutResolver(ctx context.Context) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
if err != nil { if err != nil {
log.Debug("Failed to get GinContext: ", err) log.Debug("Failed to get GinContext: ", err)
return res, err return nil, err
} }
// get fingerprint hash // get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc) fingerprintHash, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("Failed to get fingerprint hash: ", err) log.Debug("Failed to get fingerprint hash: ", err)
return res, err return nil, err
} }
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash) decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil { if err != nil {
log.Debug("Failed to decrypt fingerprint hash: ", err) log.Debug("Failed to decrypt fingerprint hash: ", err)
return res, err return nil, err
} }
fingerPrint := string(decryptedFingerPrint) var sessionData token.SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
if err != nil {
return nil, err
}
memorystore.Provider.RemoveState(fingerPrint) memorystore.Provider.DeleteUserSession(sessionData.Subject, sessionData.Nonce)
cookie.DeleteSession(gc) cookie.DeleteSession(gc)
res = &model.Response{ res := &model.Response{
Message: "Logged out successfully", Message: "Logged out successfully",
} }

View File

@ -57,12 +57,17 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
// verify if token exists in db // verify if token exists in db
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(params.Token)
if err != nil { if err != nil {
log.Debug("Failed to parse token: ", err) log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token`)
} }
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Failed to validate jwt claims: ", err)
return res, fmt.Errorf(`invalid token`)
}
email := claim["sub"].(string) email := claim["sub"].(string)
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"email": email, "email": email,

View File

@ -47,7 +47,7 @@ func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) (
return res, err return res, err
} }
go memorystore.Provider.DeleteAllUserSession(fmt.Sprintf("%x", user.ID)) go memorystore.Provider.DeleteAllUserSessions(user.ID)
res = &model.Response{ res = &model.Response{
Message: `user access revoked successfully`, Message: `user access revoked successfully`,

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -29,7 +30,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
sessionToken, err := cookie.GetSession(gc) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("Failed to get session token", err) log.Debug("Failed to get session token: ", err)
return res, errors.New("unauthorized") return res, errors.New("unauthorized")
} }
@ -76,10 +77,7 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
} }
// rollover the session for security // rollover the session for security
memorystore.Provider.RemoveState(sessionToken) go memorystore.Provider.DeleteUserSession(userID, claims.Nonce)
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
@ -94,10 +92,13 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }
return res, nil return res, nil
} }

View File

@ -225,8 +225,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err return res, err
} }
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@ -244,6 +242,15 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: userToReturn, User: userToReturn,
} }
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
} }
return res, nil return res, nil

View File

@ -142,7 +142,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
} }
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
go cookie.DeleteSession(gc) go cookie.DeleteSession(gc)
user.Email = newEmail user.Email = newEmail

View File

@ -113,7 +113,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
} }
// TODO figure out how to do this // TODO figure out how to do this
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
user.Email = newEmail user.Email = newEmail
@ -182,7 +182,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
rolesToSave = strings.Join(inputRoles, ",") rolesToSave = strings.Join(inputRoles, ",")
} }
go memorystore.Provider.DeleteAllUserSession(user.ID) go memorystore.Provider.DeleteAllUserSessions(user.ID)
} }
if rolesToSave != "" { if rolesToSave != "" {

View File

@ -4,11 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers" "github.com/authorizerdev/authorizer/server/parsers"
@ -30,48 +30,46 @@ func ValidateJwtTokenResolver(ctx context.Context, params model.ValidateJWTToken
} }
tokenType := params.TokenType tokenType := params.TokenType
if tokenType != "access_token" && tokenType != "refresh_token" && tokenType != "id_token" { if tokenType != constants.TokenTypeAccessToken && tokenType != constants.TokenTypeRefreshToken && tokenType != constants.TokenTypeIdentityToken {
log.Debug("Invalid token type: ", tokenType) log.Debug("Invalid token type: ", tokenType)
return nil, errors.New("invalid token type") return nil, errors.New("invalid token type")
} }
var claimRoles []string
var claims jwt.MapClaims
userID := "" userID := ""
nonce := "" nonce := ""
// access_token and refresh_token should be validated from session store as well
if tokenType == "access_token" || tokenType == "refresh_token" { claims, err = token.ParseJWTToken(params.Token)
savedSession, err := memorystore.Provider.GetState(params.Token) if err != nil {
if savedSession == "" || err != nil { log.Debug("Failed to parse JWT token: ", err)
return &model.ValidateJWTTokenResponse{ return nil, err
IsValid: false, }
}, nil userID = claims["sub"].(string)
// access_token and refresh_token should be validated from session store as well
if tokenType == constants.TokenTypeAccessToken || tokenType == constants.TokenTypeRefreshToken {
nonce = claims["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, tokenType+"_"+claims["nonce"].(string))
if err != nil || token == "" {
log.Debug("Failed to get user session: ", err)
return nil, errors.New("invalid token")
} }
savedSessionSplit := strings.Split(savedSession, "@")
nonce = savedSessionSplit[0]
userID = savedSessionSplit[1]
} }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
var claimRoles []string
var claims jwt.MapClaims
// we cannot validate sub and nonce in case of id_token as that token is not persisted in session store // we cannot validate nonce in case of id_token as that token is not persisted in session store
if userID != "" && nonce != "" { if nonce != "" {
claims, err = token.ParseJWTToken(params.Token, hostname, nonce, userID) if ok, err := token.ValidateJWTClaims(claims, hostname, nonce, userID); !ok || err != nil {
if err != nil {
log.Debug("Failed to parse jwt token: ", err) log.Debug("Failed to parse jwt token: ", err)
return &model.ValidateJWTTokenResponse{ return nil, errors.New("invalid claims")
IsValid: false,
}, nil
} }
} else { } else {
claims, err = token.ParseJWTTokenWithoutNonce(params.Token, hostname) if ok, err := token.ValidateJWTTokenWithoutNonce(claims, hostname, userID); !ok || err != nil {
if err != nil {
log.Debug("Failed to parse jwt token without nonce: ", err) log.Debug("Failed to parse jwt token without nonce: ", err)
return &model.ValidateJWTTokenResponse{ return nil, errors.New("invalid claims")
IsValid: false,
}, nil
} }
} }
claimRolesInterface := claims["roles"] claimRolesInterface := claims["roles"]

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -36,12 +37,17 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// verify if token exists in db // verify if token exists in db
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email) claim, err := token.ParseJWTToken(params.Token)
if err != nil { if err != nil {
log.Debug("Failed to parse token: ", err) log.Debug("Failed to parse token: ", err)
return res, fmt.Errorf(`invalid token: %s`, err.Error()) return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }
if ok, err := token.ValidateJWTClaims(claim, hostname, verificationRequest.Nonce, verificationRequest.Email); !ok || err != nil {
log.Debug("Failed to validate jwt claims: ", err)
return res, fmt.Errorf(`invalid token: %s`, err.Error())
}
email := claim["sub"].(string) email := claim["sub"].(string)
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"email": email, "email": email,
@ -75,9 +81,6 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
return res, err return res, err
} }
memorystore.Provider.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go db.Provider.AddSession(models.Session{ go db.Provider.AddSession(models.Session{
UserID: user.ID, UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request), UserAgent: utils.GetUserAgent(gc.Request),
@ -96,5 +99,14 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
ExpiresIn: &expiresIn, ExpiresIn: &expiresIn,
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
cookie.SetSession(gc, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
return res, nil return res, nil
} }

View File

@ -52,7 +52,7 @@ func TestJwt(t *testing.T) {
} }
jwtToken, err := token.SignJWTToken(expiredClaims) jwtToken, err := token.SignJWTToken(expiredClaims)
assert.NoError(t, err) assert.NoError(t, err)
_, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject) _, err = token.ParseJWTToken(jwtToken)
assert.Error(t, err, err.Error(), "Token is expired") assert.Error(t, err, err.Error(), "Token is expired")
}) })
t.Run("HMAC algorithms", func(t *testing.T) { t.Run("HMAC algorithms", func(t *testing.T) {
@ -62,27 +62,36 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("HS384", func(t *testing.T) { t.Run("HS384", func(t *testing.T) {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS384") memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS384")
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("HS512", func(t *testing.T) { t.Run("HS512", func(t *testing.T) {
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS512") memorystore.Provider.UpdateEnvVariable(constants.EnvKeyJwtType, "HS512")
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })
@ -96,9 +105,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("RS384", func(t *testing.T) { t.Run("RS384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID) _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID)
@ -109,9 +121,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("RS512", func(t *testing.T) { t.Run("RS512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID) _, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID)
@ -122,9 +137,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })
@ -138,9 +156,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("ES384", func(t *testing.T) { t.Run("ES384", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID) _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID)
@ -151,9 +172,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
t.Run("ES512", func(t *testing.T) { t.Run("ES512", func(t *testing.T) {
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID) _, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID)
@ -164,9 +188,12 @@ func TestJwt(t *testing.T) {
jwtToken, err := token.SignJWTToken(claims) jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, jwtToken) assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject) c, err := token.ParseJWTToken(jwtToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"]) assert.Equal(t, c["email"].(string), claims["email"])
valid, err := token.ValidateJWTClaims(c, hostname, nonce, subject)
assert.NoError(t, err)
assert.True(t, valid)
}) })
}) })

View File

@ -2,6 +2,7 @@ package test
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@ -9,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -27,15 +29,19 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
token := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) assert.NotEmpty(t, accessToken)
cookie := ""
// set all they keys in cookie one of them should be session cookie claims, err := token.ParseJWTToken(accessToken)
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, claims)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
} assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx) _, err = resolvers.LogoutResolver(ctx)

View File

@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -33,19 +34,21 @@ func sessionTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
sessions := memorystore.Provider.GetUserSessions(verifyRes.User.ID) accessToken := *verifyRes.AccessToken
cookie := "" assert.NotEmpty(t, accessToken)
token := *verifyRes.AccessToken
// set all they keys in cookie one of them should be session cookie claims, err := token.ParseJWTToken(accessToken)
for key := range sessions { assert.NoError(t, err)
if key != token { assert.NotEmpty(t, claims)
cookie += fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", key)
} sessionToken, err := memorystore.Provider.GetUserSession(verifyRes.User.ID, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
} assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";") cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie) req.Header.Set("Cookie", cookie)
_, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{}) _, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{})
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
@ -22,12 +23,14 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
TokenType: "access_token", TokenType: "access_token",
Token: "", Token: "",
}) })
assert.False(t, res.IsValid) assert.Error(t, err)
assert.Nil(t, res)
res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ res, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
TokenType: "access_token", TokenType: "access_token",
Token: "invalid", Token: "invalid",
}) })
assert.False(t, res.IsValid) assert.Error(t, err)
assert.Nil(t, res)
_, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ _, err = resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
TokenType: "access_token_invalid", TokenType: "access_token_invalid",
Token: "invalid@invalid", Token: "invalid@invalid",
@ -48,8 +51,12 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
assert.NoError(t, err) assert.NoError(t, err)
authToken, err := token.CreateAuthToken(gc, user, roles, scope) authToken, err := token.CreateAuthToken(gc, user, roles, scope)
memorystore.Provider.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetState(authToken.RefreshToken.Token, authToken.FingerPrint+"@"+user.ID) memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
memorystore.Provider.SetUserSession(user.ID, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
t.Run(`should validate the access token`, func(t *testing.T) { t.Run(`should validate the access token`, func(t *testing.T) {
res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{ res, err := resolvers.ValidateJwtTokenResolver(ctx, model.ValidateJWTTokenInput{
@ -57,7 +64,6 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
Token: authToken.AccessToken.Token, Token: authToken.AccessToken.Token,
Roles: []string{"user"}, Roles: []string{"user"},
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, res.IsValid) assert.True(t, res.IsValid)

View File

@ -198,18 +198,24 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(accessToken) res, err := ParseJWTToken(accessToken)
if savedSession == "" || err != nil { if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeAccessToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSessionSplit := strings.Split(savedSession, "@") if token != accessToken {
nonce := savedSessionSplit[0] return res, fmt.Errorf(`unauthorized`)
userID := savedSessionSplit[1] }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(accessToken, hostname, nonce, userID) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
if err != nil {
return res, err return res, err
} }
@ -228,18 +234,24 @@ func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]inte
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(refreshToken) res, err := ParseJWTToken(refreshToken)
if savedSession == "" || err != nil { if err != nil {
return res, err
}
userID := res["sub"].(string)
nonce := res["nonce"].(string)
token, err := memorystore.Provider.GetUserSession(userID, constants.TokenTypeRefreshToken+"_"+nonce)
if nonce == "" || err != nil {
return res, fmt.Errorf(`unauthorized`) return res, fmt.Errorf(`unauthorized`)
} }
savedSessionSplit := strings.Split(savedSession, "@") if token != refreshToken {
nonce := savedSessionSplit[0] return res, fmt.Errorf(`unauthorized`)
userID := savedSessionSplit[1] }
hostname := parsers.GetHost(gc) hostname := parsers.GetHost(gc)
res, err = ParseJWTToken(refreshToken, hostname, nonce, userID) if ok, err := ValidateJWTClaims(res, hostname, nonce, userID); !ok || err != nil {
if err != nil {
return res, err return res, err
} }
@ -255,15 +267,6 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, fmt.Errorf(`unauthorized`) return nil, fmt.Errorf(`unauthorized`)
} }
savedSession, err := memorystore.Provider.GetState(encryptedSession)
if savedSession == "" || err != nil {
return nil, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession) decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
if err != nil { if err != nil {
return nil, err return nil, err
@ -275,23 +278,20 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
return nil, err return nil, err
} }
if res.Nonce != nonce { token, err := memorystore.Provider.GetUserSession(res.Subject, constants.TokenTypeSessionToken+"_"+res.Nonce)
return nil, fmt.Errorf(`unauthorized: invalid nonce`) if token == "" || err != nil {
log.Debug("invalid browser session:", err)
return nil, fmt.Errorf(`unauthorized`)
} }
if res.Subject != userID { if encryptedSession != token {
return nil, fmt.Errorf(`unauthorized: invalid user id`) return nil, fmt.Errorf(`unauthorized: invalid nonce`)
} }
if res.ExpiresAt < time.Now().Unix() { if res.ExpiresAt < time.Now().Unix() {
return nil, fmt.Errorf(`unauthorized: token expired`) return nil, fmt.Errorf(`unauthorized: token expired`)
} }
// TODO validate scope
// if !reflect.DeepEqual(res.Roles, roles) {
// return res, "", fmt.Errorf(`unauthorized`)
// }
return &res, nil return &res, nil
} }

View File

@ -60,7 +60,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
} }
// ParseJWTToken common util to parse jwt token // ParseJWTToken common util to parse jwt token
func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) { func ParseJWTToken(token string) (jwt.MapClaims, error) {
jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType) jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,98 +116,51 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
intIat := int64(claims["iat"].(float64)) intIat := int64(claims["iat"].(float64))
claims["exp"] = intExp claims["exp"] = intExp
claims["iat"] = intIat claims["iat"] = intIat
return claims, nil
}
// ValidateJWTClaims common util to validate claims
func ValidateJWTClaims(claims jwt.MapClaims, hostname, nonce, subject string) (bool, error) {
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil { if err != nil {
return claims, err return false, err
} }
if claims["aud"] != clientID { if claims["aud"] != clientID {
return claims, errors.New("invalid audience") return false, errors.New("invalid audience")
} }
if claims["nonce"] != nonce { if claims["nonce"] != nonce {
return claims, errors.New("invalid nonce") return false, errors.New("invalid nonce")
} }
if claims["iss"] != hostname { if claims["iss"] != hostname {
return claims, errors.New("invalid issuer") return false, errors.New("invalid issuer")
} }
if claims["sub"] != subject { if claims["sub"] != subject {
return claims, errors.New("invalid subject") return false, errors.New("invalid subject")
} }
return claims, nil return true, nil
} }
// ParseJWTTokenWithoutNonce common util to parse jwt token without nonce // ValidateJWTTokenWithoutNonce common util to validate claims without nonce
// used to validate ID token as it is not persisted in store func ValidateJWTTokenWithoutNonce(claims jwt.MapClaims, hostname, subject string) (bool, error) {
func ParseJWTTokenWithoutNonce(token, hostname string) (jwt.MapClaims, error) {
jwtType, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
if err != nil {
return nil, err
}
signingMethod := jwt.GetSigningMethod(jwtType)
var claims jwt.MapClaims
switch signingMethod {
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)
if err != nil {
return nil, err
}
return []byte(jwtSecret), nil
})
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return nil, err
}
key, err := crypto.ParseRsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return nil, err
}
return key, nil
})
case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
jwtPublicKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
if err != nil {
return nil, err
}
key, err := crypto.ParseEcdsaPublicKeyFromPemStr(jwtPublicKey)
if err != nil {
return nil, err
}
return key, nil
})
default:
err = errors.New("unsupported signing method")
}
if err != nil {
return claims, err
}
// claim parses exp & iat into float 64 with e^10,
// but we expect it to be int64
// hence we need to assert interface and convert to int64
intExp := int64(claims["exp"].(float64))
intIat := int64(claims["iat"].(float64))
claims["exp"] = intExp
claims["iat"] = intIat
clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID) clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
if err != nil { if err != nil {
return claims, err return false, err
} }
if claims["aud"] != clientID { if claims["aud"] != clientID {
return claims, errors.New("invalid audience") return false, errors.New("invalid audience")
} }
if claims["iss"] != hostname { if claims["iss"] != hostname {
return claims, errors.New("invalid issuer") return false, errors.New("invalid issuer")
} }
return claims, nil if claims["sub"] != subject {
return false, errors.New("invalid subject")
}
return true, nil
} }