fix: auth flow
This commit is contained in:
@@ -2,23 +2,23 @@ package token
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/cookie"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/robertkrimen/otto"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/crypto"
|
||||
"github.com/authorizerdev/authorizer/server/db/models"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
)
|
||||
|
||||
// JWTToken is a struct to hold JWT token and its expiration time
|
||||
@@ -33,60 +33,219 @@ type Token struct {
|
||||
FingerPrintHash string `json:"fingerprint_hash"`
|
||||
RefreshToken *JWTToken `json:"refresh_token"`
|
||||
AccessToken *JWTToken `json:"access_token"`
|
||||
IDToken *JWTToken `json:"id_token"`
|
||||
}
|
||||
|
||||
// SessionData
|
||||
type SessionData struct {
|
||||
Subject string `json:"sub"`
|
||||
Roles []string `json:"roles"`
|
||||
Scope []string `json:"scope"`
|
||||
Nonce string `json:"nonce"`
|
||||
IssuedAt int64 `json:"iat"`
|
||||
ExpiresAt int64 `json:"exp"`
|
||||
}
|
||||
|
||||
// CreateSessionToken creates a new session token
|
||||
func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) {
|
||||
fingerPrintMap := &SessionData{
|
||||
Nonce: nonce,
|
||||
Roles: roles,
|
||||
Subject: user.ID,
|
||||
Scope: scope,
|
||||
IssuedAt: time.Now().Unix(),
|
||||
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
|
||||
}
|
||||
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
|
||||
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return fingerPrintMap, fingerPrintHash, nil
|
||||
}
|
||||
|
||||
// CreateAuthToken creates a new auth token when userlogs in
|
||||
func CreateAuthToken(user models.User, roles []string) (*Token, error) {
|
||||
fingerprint := uuid.NewString()
|
||||
fingerPrintHashBytes, err := crypto.EncryptAES([]byte(fingerprint))
|
||||
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) {
|
||||
hostname := utils.GetHost(gc)
|
||||
nonce := uuid.New().String()
|
||||
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles)
|
||||
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles)
|
||||
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
FingerPrint: fingerprint,
|
||||
FingerPrintHash: string(fingerPrintHashBytes),
|
||||
RefreshToken: &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt},
|
||||
res := &Token{
|
||||
FingerPrint: nonce,
|
||||
FingerPrintHash: fingerPrintHash,
|
||||
AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt},
|
||||
}, nil
|
||||
IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt},
|
||||
}
|
||||
|
||||
if utils.StringSliceContains(scope, "offline_access") {
|
||||
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.RefreshToken = &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CreateRefreshToken util to create JWT token
|
||||
func CreateRefreshToken(user models.User, roles []string) (string, int64, error) {
|
||||
func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
|
||||
// expires in 1 year
|
||||
expiryBound := time.Hour * 8760
|
||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||
|
||||
customClaims := jwt.MapClaims{
|
||||
"iss": "",
|
||||
"aud": "",
|
||||
"iss": hostname,
|
||||
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
|
||||
"sub": user.ID,
|
||||
"exp": expiresAt,
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": constants.TokenTypeRefreshToken,
|
||||
"roles": roles,
|
||||
"id": user.ID,
|
||||
"nonce": nonce,
|
||||
}
|
||||
|
||||
token, err := SignJWTToken(customClaims)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
|
||||
// CreateAccessToken util to create JWT token, based on
|
||||
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||
func CreateAccessToken(user models.User, roles []string) (string, int64, error) {
|
||||
func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) {
|
||||
expiryBound := time.Minute * 30
|
||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||
|
||||
customClaims := jwt.MapClaims{
|
||||
"iss": hostName,
|
||||
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
|
||||
"nonce": nonce,
|
||||
"sub": user.ID,
|
||||
"exp": expiresAt,
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": constants.TokenTypeAccessToken,
|
||||
"scope": scopes,
|
||||
"roles": roles,
|
||||
}
|
||||
|
||||
token, err := SignJWTToken(customClaims)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
|
||||
// GetAccessToken returns the access token from the request (either from header or cookie)
|
||||
func GetAccessToken(gc *gin.Context) (string, error) {
|
||||
// try to check in auth header for cookie
|
||||
auth := gc.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return "", fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(auth, "Bearer ") {
|
||||
return "", fmt.Errorf(`not a bearer token`)
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(auth, "Bearer ")
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Function to validate access token for authorizer apis (profile, update_profile)
|
||||
func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interface{}, error) {
|
||||
var res map[string]interface{}
|
||||
|
||||
if accessToken == "" {
|
||||
return res, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSession := sessionstore.GetState(accessToken)
|
||||
if savedSession == "" {
|
||||
return res, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSessionSplit := strings.Split(savedSession, "@")
|
||||
nonce := savedSessionSplit[0]
|
||||
userID := savedSessionSplit[1]
|
||||
|
||||
hostname := utils.GetHost(gc)
|
||||
res, err := ParseJWTToken(accessToken, hostname, nonce, userID)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if res["token_type"] != constants.TokenTypeAccessToken {
|
||||
return res, fmt.Errorf(`unauthorized: invalid token type`)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) {
|
||||
if encryptedSession == "" {
|
||||
return nil, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSession := sessionstore.GetState(encryptedSession)
|
||||
if savedSession == "" {
|
||||
return nil, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSessionSplit := strings.Split(savedSession, "@")
|
||||
nonce := savedSessionSplit[0]
|
||||
userID := savedSessionSplit[1]
|
||||
|
||||
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res SessionData
|
||||
err = json.Unmarshal([]byte(decryptedFingerPrint), &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.Nonce != nonce {
|
||||
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
|
||||
}
|
||||
|
||||
if res.Subject != userID {
|
||||
return nil, fmt.Errorf(`unauthorized: invalid user id`)
|
||||
}
|
||||
|
||||
if res.ExpiresAt < time.Now().Unix() {
|
||||
return nil, fmt.Errorf(`unauthorized: token expired`)
|
||||
}
|
||||
|
||||
// TODO validate scope
|
||||
// if !reflect.DeepEqual(res.Roles, roles) {
|
||||
// return res, "", fmt.Errorf(`unauthorized`)
|
||||
// }
|
||||
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// CreateIDToken util to create JWT token, based on
|
||||
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||
func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
|
||||
expiryBound := time.Minute * 30
|
||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||
|
||||
@@ -97,13 +256,13 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
|
||||
|
||||
claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
|
||||
customClaims := jwt.MapClaims{
|
||||
"iss": "",
|
||||
"iss": hostname,
|
||||
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
|
||||
"nonce": "",
|
||||
"nonce": nonce,
|
||||
"sub": user.ID,
|
||||
"exp": expiresAt,
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": constants.TokenTypeAccessToken,
|
||||
"token_type": constants.TokenTypeIdentityToken,
|
||||
"allowed_roles": strings.Split(user.Roles, ","),
|
||||
claimKey: roles,
|
||||
}
|
||||
@@ -152,58 +311,18 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
|
||||
// GetAccessToken returns the access token from the request (either from header or cookie)
|
||||
func GetAccessToken(gc *gin.Context) (string, error) {
|
||||
token, err := cookie.GetAccessTokenCookie(gc)
|
||||
if err != nil || token == "" {
|
||||
// try to check in auth header for cookie
|
||||
auth := gc.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return "", fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
token = strings.TrimPrefix(auth, "Bearer ")
|
||||
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetRefreshToken returns the refresh token from cookie / request query url
|
||||
func GetRefreshToken(gc *gin.Context) (string, error) {
|
||||
token, err := cookie.GetRefreshTokenCookie(gc)
|
||||
|
||||
if err != nil || token == "" {
|
||||
// GetIDToken returns the id token from the request header
|
||||
func GetIDToken(gc *gin.Context) (string, error) {
|
||||
// try to check in auth header for cookie
|
||||
auth := gc.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return "", fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(auth, "Bearer ") {
|
||||
return "", fmt.Errorf(`not a bearer token`)
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(auth, "Bearer ")
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// GetFingerPrint returns the finger print from cookie
|
||||
func GetFingerPrint(gc *gin.Context) (string, error) {
|
||||
fingerPrint, err := cookie.GetFingerPrintCookie(gc)
|
||||
if err != nil || fingerPrint == "" {
|
||||
return "", fmt.Errorf(`no finger print`)
|
||||
}
|
||||
return fingerPrint, nil
|
||||
}
|
||||
|
||||
func ValidateAccessToken(gc *gin.Context) (map[string]interface{}, error) {
|
||||
token, err := GetAccessToken(gc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
claims, err := ParseJWTToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// also validate if there is user session present with access token
|
||||
sessions := sessionstore.GetUserSessions(claims["id"].(string))
|
||||
if len(sessions) == 0 {
|
||||
return nil, errors.New("unauthorized")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
|
||||
}
|
||||
|
||||
// ParseJWTToken common util to parse jwt token
|
||||
func ParseJWTToken(token string) (jwt.MapClaims, error) {
|
||||
func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) {
|
||||
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
|
||||
signingMethod := jwt.GetSigningMethod(jwtType)
|
||||
|
||||
@@ -87,5 +87,21 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) {
|
||||
claims["exp"] = intExp
|
||||
claims["iat"] = intIat
|
||||
|
||||
if claims["aud"] != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||
return claims, errors.New("invalid audience")
|
||||
}
|
||||
|
||||
if claims["nonce"] != nonce {
|
||||
return claims, errors.New("invalid nonce")
|
||||
}
|
||||
|
||||
if claims["iss"] != hostname {
|
||||
return claims, errors.New("invalid issuer")
|
||||
}
|
||||
|
||||
if claims["sub"] != subject {
|
||||
return claims, errors.New("invalid subject")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
@@ -9,13 +9,15 @@ import (
|
||||
)
|
||||
|
||||
// CreateVerificationToken creates a verification JWT token
|
||||
func CreateVerificationToken(email, tokenType, hostname string) (string, error) {
|
||||
func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) {
|
||||
claims := jwt.MapClaims{
|
||||
"iss": hostname,
|
||||
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
|
||||
"sub": email,
|
||||
"exp": time.Now().Add(time.Minute * 30).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": tokenType,
|
||||
"email": email,
|
||||
"host": hostname,
|
||||
"nonce": nonceHash,
|
||||
"redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL),
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user