diff --git a/app/src/Root.tsx b/app/src/Root.tsx
index abffef6..61dd2a8 100644
--- a/app/src/Root.tsx
+++ b/app/src/Root.tsx
@@ -57,7 +57,6 @@ export default function Root({
urlProps.redirect_uri = urlProps.redirectURL;
useEffect(() => {
- console.log(config);
if (token) {
let redirectURL = config.redirectURL || '/app';
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
@@ -113,7 +112,7 @@ export default function Root({
-
+
diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go
index c841b05..fadea64 100644
--- a/server/graph/generated/generated.go
+++ b/server/graph/generated/generated.go
@@ -2278,6 +2278,10 @@ input VerifyEmailInput {
input ResendVerifyEmailInput {
email: String!
identifier: String!
+ # state is used for authorization code grant flow
+ # it is used to get code for an on-going auth process during login
+ # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
+ state: String
}
input UpdateProfileInput {
@@ -2425,10 +2429,18 @@ input DeleteEmailTemplateRequest {
input VerifyOTPRequest {
email: String!
otp: String!
+ # state is used for authorization code grant flow
+ # it is used to get code for an on-going auth process during login
+ # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
+ state: String
}
input ResendOTPRequest {
email: String!
+ # state is used for authorization code grant flow
+ # it is used to get code for an on-going auth process during login
+ # and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
+ state: String
}
type Mutation {
@@ -14679,7 +14691,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
asMap[k] = v
}
- fieldsInOrder := [...]string{"email"}
+ fieldsInOrder := [...]string{"email", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -14694,6 +14706,14 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
if err != nil {
return it, err
}
+ case "state":
+ var err error
+
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
+ it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
+ if err != nil {
+ return it, err
+ }
}
}
@@ -14707,7 +14727,7 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con
asMap[k] = v
}
- fieldsInOrder := [...]string{"email", "identifier"}
+ fieldsInOrder := [...]string{"email", "identifier", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -14730,6 +14750,14 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con
if err != nil {
return it, err
}
+ case "state":
+ var err error
+
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
+ it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
+ if err != nil {
+ return it, err
+ }
}
}
@@ -15879,7 +15907,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
asMap[k] = v
}
- fieldsInOrder := [...]string{"email", "otp"}
+ fieldsInOrder := [...]string{"email", "otp", "state"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
@@ -15902,6 +15930,14 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
if err != nil {
return it, err
}
+ case "state":
+ var err error
+
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
+ it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
+ if err != nil {
+ return it, err
+ }
}
}
diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go
index 1fe5db6..c1fb6c5 100644
--- a/server/graph/model/models_gen.go
+++ b/server/graph/model/models_gen.go
@@ -200,12 +200,14 @@ type PaginationInput struct {
}
type ResendOTPRequest struct {
- Email string `json:"email"`
+ Email string `json:"email"`
+ State *string `json:"state"`
}
type ResendVerifyEmailInput struct {
- Email string `json:"email"`
- Identifier string `json:"identifier"`
+ Email string `json:"email"`
+ Identifier string `json:"identifier"`
+ State *string `json:"state"`
}
type ResetPasswordInput struct {
@@ -415,8 +417,9 @@ type VerifyEmailInput struct {
}
type VerifyOTPRequest struct {
- Email string `json:"email"`
- Otp string `json:"otp"`
+ Email string `json:"email"`
+ Otp string `json:"otp"`
+ State *string `json:"state"`
}
type Webhook struct {
diff --git a/server/handlers/authorize.go b/server/handlers/authorize.go
index 171e343..c3e2e2a 100644
--- a/server/handlers/authorize.go
+++ b/server/handlers/authorize.go
@@ -45,9 +45,7 @@ import (
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/memorystore"
- "github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/token"
- "github.com/authorizerdev/authorizer/server/utils"
)
// Check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
@@ -108,19 +106,11 @@ func AuthorizeHandler() gin.HandlerFunc {
}
log := log.WithFields(log.Fields{
- "response_mode": responseMode,
- "response_type": responseType,
- "state": state,
- "code_challenge": codeChallenge,
- "scope": scope,
- "redirect_uri": redirectURI,
- "nonce": nonce,
- "code": code,
+ "response_mode": responseMode,
+ "response_type": responseType,
})
- // memorystore.Provider.SetState(codeChallenge, code)
// TODO add state with timeout
-
// used for response mode query or fragment
loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
if responseType == constants.ResponseTypeCode {
@@ -141,17 +131,6 @@ func AuthorizeHandler() gin.HandlerFunc {
loginURL = "/app#" + loginState
}
- if state == "" {
- handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
- "type": "authorization_response",
- "response": map[string]interface{}{
- "error": "state_required",
- "error_description": "state is required",
- },
- }, http.StatusOK)
- return
- }
-
if responseType == constants.ResponseTypeCode && codeChallenge == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response",
@@ -275,7 +254,6 @@ func AuthorizeHandler() gin.HandlerFunc {
}
if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken {
- hostname := parsers.GetHost(gc)
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "")
if err != nil {
@@ -299,7 +277,7 @@ func AuthorizeHandler() gin.HandlerFunc {
cookie.SetSession(gc, authToken.FingerPrintHash)
// used of query mode
- params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&code=" + code
+ params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
@@ -308,19 +286,17 @@ func AuthorizeHandler() gin.HandlerFunc {
"scope": scope,
"token_type": "Bearer",
"expires_in": authToken.AccessToken.ExpiresAt,
- "code": code,
}
- if utils.StringSliceContains(scope, "offline_access") {
- refreshToken, _, err := token.CreateRefreshToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod)
- if err != nil {
- log.Debug("SetUserSession failed: ", err)
- handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
- return
- }
- res["refresh_token"] = refreshToken
- params += "&refresh_token=" + refreshToken
- memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce, refreshToken)
+ if nonce != "" {
+ params += "&nonce=" + nonce
+ res["nonce"] = nonce
+ }
+
+ if authToken.RefreshToken != nil {
+ res["refresh_token"] = authToken.RefreshToken.Token
+ params += "&refresh_token=" + authToken.RefreshToken.Token
+ memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
if responseMode == constants.ResponseModeQuery {
@@ -349,6 +325,9 @@ func AuthorizeHandler() gin.HandlerFunc {
}
func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error {
+ if strings.TrimSpace(state) == "" {
+ return fmt.Errorf("invalid state. state is required to prevent csrf attack", responseMode)
+ }
if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken {
return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode)
}
@@ -387,8 +366,6 @@ func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string,
})
return
case constants.ResponseModeFormPost:
- fmt.Println("=> trying tof orm post")
- fmt.Printf("=> %+v \n", data["response"])
gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{
"target_origin": redirectURI,
"authorization_response": data["response"],
diff --git a/server/handlers/oauth_callback.go b/server/handlers/oauth_callback.go
index 2dfe2c5..8bd894c 100644
--- a/server/handlers/oauth_callback.go
+++ b/server/handlers/oauth_callback.go
@@ -56,20 +56,20 @@ func OAuthCallbackHandler() gin.HandlerFunc {
scopes := strings.Split(sessionSplit[3], ",")
user := models.User{}
- code := ctx.Request.FormValue("code")
+ oauthCode := ctx.Request.FormValue("code")
switch provider {
case constants.AuthRecipeMethodGoogle:
- user, err = processGoogleUserInfo(code)
+ user, err = processGoogleUserInfo(oauthCode)
case constants.AuthRecipeMethodGithub:
- user, err = processGithubUserInfo(code)
+ user, err = processGithubUserInfo(oauthCode)
case constants.AuthRecipeMethodFacebook:
- user, err = processFacebookUserInfo(code)
+ user, err = processFacebookUserInfo(oauthCode)
case constants.AuthRecipeMethodLinkedIn:
- user, err = processLinkedInUserInfo(code)
+ user, err = processLinkedInUserInfo(oauthCode)
case constants.AuthRecipeMethodApple:
- user, err = processAppleUserInfo(code)
+ user, err = processAppleUserInfo(oauthCode)
case constants.AuthRecipeMethodTwitter:
- user, err = processTwitterUserInfo(code, sessionState)
+ user, err = processTwitterUserInfo(oauthCode, sessionState)
default:
log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`)
@@ -200,19 +200,50 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// TODO
// use stateValue to get code / nonce
// add code / nonce to id_token
- nonce := uuid.New().String()
- authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, "")
+ code := ""
+ codeChallenge := ""
+ nonce := ""
+ if stateValue != "" {
+ // Get state from store
+ authorizeState, _ := memorystore.Provider.GetState(stateValue)
+ if authorizeState != "" {
+ authorizeStateSplit := strings.Split(authorizeState, "@@")
+ if len(authorizeStateSplit) > 1 {
+ code = authorizeStateSplit[0]
+ codeChallenge = authorizeStateSplit[1]
+ } else {
+ nonce = authorizeState
+ }
+ go memorystore.Provider.RemoveState(stateValue)
+ }
+ }
+ if nonce == "" {
+ nonce = uuid.New().String()
+ }
+ authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, code)
if err != nil {
log.Debug("Failed to create auth token: ", err)
ctx.JSON(500, gin.H{"error": err.Error()})
}
+ // Code challenge could be optional if PKCE flow is not used
+ if code != "" {
+ if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ log.Debug("SetState failed: ", err)
+ ctx.JSON(500, gin.H{"error": err.Error()})
+ }
+ }
+
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
}
- 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 + "&nonce=" + nonce
+
+ if code != "" {
+ params += "&code=" + code
+ }
sessionKey := provider + ":" + user.ID
cookie.SetSession(ctx, authToken.FingerPrintHash)
@@ -220,7 +251,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil {
- params = params + `&refresh_token=` + authToken.RefreshToken.Token
+ params += `&refresh_token=` + authToken.RefreshToken.Token
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
}
diff --git a/server/handlers/token.go b/server/handlers/token.go
index 5e80ada..b515dd5 100644
--- a/server/handlers/token.go
+++ b/server/handlers/token.go
@@ -3,7 +3,6 @@ package handlers
import (
"crypto/sha256"
"encoding/base64"
- "fmt"
"net/http"
"strings"
"time"
@@ -33,10 +32,6 @@ type RequestBody struct {
// grant type required
func TokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
- // body := gc.Request.Body
- // x, _ := ioutil.ReadAll(body)
-
- // fmt.Printf("=> %s \n %s\n", string(x), gc.Request.Header.Get("Content-Type"))
var reqBody RequestBody
if err := gc.Bind(&reqBody); err != nil {
log.Debug("Error binding JSON: ", err)
@@ -47,8 +42,6 @@ func TokenHandler() gin.HandlerFunc {
return
}
- fmt.Printf("=>req body: %+v\n", reqBody)
-
codeVerifier := strings.TrimSpace(reqBody.CodeVerifier)
code := strings.TrimSpace(reqBody.Code)
clientID := strings.TrimSpace(reqBody.ClientID)
@@ -125,7 +118,6 @@ func TokenHandler() gin.HandlerFunc {
// [0] -> code_challenge
// [1] -> session cookie
sessionDataSplit := strings.Split(sessionData, "@@")
- fmt.Println("=> sessionDataSplit:", sessionDataSplit)
go memorystore.Provider.RemoveState(code)
@@ -135,7 +127,6 @@ func TokenHandler() gin.HandlerFunc {
encryptedCode := strings.ReplaceAll(base64.RawURLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
- fmt.Println("=> encryptedCode", encryptedCode)
if encryptedCode != sessionDataSplit[0] {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
@@ -166,8 +157,6 @@ func TokenHandler() gin.HandlerFunc {
return
}
- fmt.Printf("=>claims: %+v\n", &claims)
-
userID = claims.Subject
roles = claims.Roles
scope = claims.Scope
@@ -242,10 +231,6 @@ func TokenHandler() gin.HandlerFunc {
}
nonce := uuid.New().String() + "@@" + code
-
- fmt.Println("=> code", code)
- fmt.Println("=> nonce", nonce)
-
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
if err != nil {
log.Debug("Error creating auth token: ", err)
diff --git a/server/handlers/verify_email.go b/server/handlers/verify_email.go
index c57d0e2..cf5ec1a 100644
--- a/server/handlers/verify_email.go
+++ b/server/handlers/verify_email.go
@@ -100,8 +100,29 @@ func VerifyEmailHandler() gin.HandlerFunc {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
}
- nonce := uuid.New().String()
- authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, "")
+ code := ""
+ // Not required as /oauth/token cannot be resumed from other tab
+ // codeChallenge := ""
+ nonce := ""
+ if state != "" {
+ // Get state from store
+ authorizeState, _ := memorystore.Provider.GetState(state)
+ if authorizeState != "" {
+ authorizeStateSplit := strings.Split(authorizeState, "@@")
+ if len(authorizeStateSplit) > 1 {
+ code = authorizeStateSplit[0]
+ // Not required as /oauth/token cannot be resumed from other tab
+ // codeChallenge = authorizeStateSplit[1]
+ } else {
+ nonce = authorizeState
+ }
+ go memorystore.Provider.RemoveState(state)
+ }
+ }
+ if nonce == "" {
+ nonce = uuid.New().String()
+ }
+ authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, code)
if err != nil {
log.Debug("Error creating auth token: ", err)
errorRes["error_description"] = err.Error()
@@ -109,12 +130,27 @@ func VerifyEmailHandler() gin.HandlerFunc {
return
}
+ // Code challenge could be optional if PKCE flow is not used
+ // Not required as /oauth/token cannot be resumed from other tab
+ // if code != "" {
+ // if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ // log.Debug("Error setting code state ", err)
+ // errorRes["error_description"] = err.Error()
+ // c.JSON(500, errorRes)
+ // return
+ // }
+ // }
+
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
}
- 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 + "&nonce=" + nonce
+
+ if code != "" {
+ params += "&code=" + code
+ }
sessionKey := loginMethod + ":" + user.ID
cookie.SetSession(c, authToken.FingerPrintHash)
diff --git a/server/resolvers/login.go b/server/resolvers/login.go
index 6a724a8..4bae30a 100644
--- a/server/resolvers/login.go
+++ b/server/resolvers/login.go
@@ -141,10 +141,9 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}, nil
}
- nonce := uuid.New().String()
- fmt.Println("=> state", refs.StringValue(params.State))
code := ""
codeChallenge := ""
+ nonce := ""
if params.State != nil {
// Get state from store
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
@@ -153,8 +152,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
if len(authorizeStateSplit) > 1 {
code = authorizeStateSplit[0]
codeChallenge = authorizeStateSplit[1]
-
- fmt.Println("=> code info", authorizeStateSplit)
} else {
nonce = authorizeState
}
@@ -162,12 +159,25 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}
}
+ if nonce == "" {
+ nonce = uuid.New().String()
+ }
+
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
if err != nil {
log.Debug("Failed to create auth token", err)
return res, err
}
+ // TODO add to other login options as well
+ // Code challenge could be optional if PKCE flow is not used
+ if code != "" {
+ if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ log.Debug("SetState failed: ", err)
+ return res, err
+ }
+ }
+
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
@@ -185,15 +195,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
- // TODO add to other login options as well
- // Code challenge could be optional if PKCE flow is not used
- if code != "" {
- fmt.Println("=> setting the state here....")
- if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
- log.Debug("SetState failed: ", err)
- return res, err
- }
- }
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
diff --git a/server/resolvers/signup.go b/server/resolvers/signup.go
index 3df520f..43f2a96 100644
--- a/server/resolvers/signup.go
+++ b/server/resolvers/signup.go
@@ -34,16 +34,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err
}
- code := ""
- if params.State != nil {
- // Get state from store
- code, err = memorystore.Provider.GetState(*params.State)
- if err != nil {
- log.Debug("Invalid Error State:", err)
- return res, fmt.Errorf("invalid_state: %s", err.Error())
- }
- }
-
isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp)
if err != nil {
log.Debug("Error getting signup disabled: ", err)
@@ -253,9 +243,26 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
scope = params.Scope
}
- nonce := uuid.New().String()
- if code != "" {
- nonce = nonce + "@@" + code
+ code := ""
+ codeChallenge := ""
+ nonce := ""
+ if params.State != nil {
+ // Get state from store
+ authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
+ if authorizeState != "" {
+ authorizeStateSplit := strings.Split(authorizeState, "@@")
+ if len(authorizeStateSplit) > 1 {
+ code = authorizeStateSplit[0]
+ codeChallenge = authorizeStateSplit[1]
+ } else {
+ nonce = authorizeState
+ }
+ go memorystore.Provider.RemoveState(refs.StringValue(params.State))
+ }
+ }
+
+ if nonce == "" {
+ nonce = uuid.New().String()
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
@@ -264,6 +271,14 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err
}
+ // Code challenge could be optional if PKCE flow is not used
+ if code != "" {
+ if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ log.Debug("SetState failed: ", err)
+ return res, err
+ }
+ }
+
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 {
expiresIn = 1
diff --git a/server/resolvers/verify_email.go b/server/resolvers/verify_email.go
index 7dbf404..47b4429 100644
--- a/server/resolvers/verify_email.go
+++ b/server/resolvers/verify_email.go
@@ -16,6 +16,7 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
+ "github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
@@ -85,13 +86,42 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
- nonce := uuid.New().String()
- authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "")
+ code := ""
+ // Not required as /oauth/token cannot be resumed from other tab
+ // codeChallenge := ""
+ nonce := ""
+ if params.State != nil {
+ // Get state from store
+ authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
+ if authorizeState != "" {
+ authorizeStateSplit := strings.Split(authorizeState, "@@")
+ if len(authorizeStateSplit) > 1 {
+ code = authorizeStateSplit[0]
+ // Not required as /oauth/token cannot be resumed from other tab
+ // codeChallenge = authorizeStateSplit[1]
+ } else {
+ nonce = authorizeState
+ }
+ go memorystore.Provider.RemoveState(refs.StringValue(params.State))
+ }
+ }
+ if nonce == "" {
+ nonce = uuid.New().String()
+ }
+ authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
}
+ // Code challenge could be optional if PKCE flow is not used
+ // Not required as /oauth/token cannot be resumed from other tab
+ // if code != "" {
+ // if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ // log.Debug("SetState failed: ", err)
+ // return res, err
+ // }
+ // }
go func() {
if isSignUp {
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user)
diff --git a/server/resolvers/verify_otp.go b/server/resolvers/verify_otp.go
index fd70a9f..016678d 100644
--- a/server/resolvers/verify_otp.go
+++ b/server/resolvers/verify_otp.go
@@ -12,6 +12,7 @@ import (
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
+ "github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
@@ -58,13 +59,40 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
- nonce := uuid.New().String()
- authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "")
+ code := ""
+ codeChallenge := ""
+ nonce := ""
+ if params.State != nil {
+ // Get state from store
+ authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
+ if authorizeState != "" {
+ authorizeStateSplit := strings.Split(authorizeState, "@@")
+ if len(authorizeStateSplit) > 1 {
+ code = authorizeStateSplit[0]
+ codeChallenge = authorizeStateSplit[1]
+ } else {
+ nonce = authorizeState
+ }
+ go memorystore.Provider.RemoveState(refs.StringValue(params.State))
+ }
+ }
+ if nonce == "" {
+ nonce = uuid.New().String()
+ }
+ authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
if err != nil {
log.Debug("Failed to create auth token: ", err)
return res, err
}
+ // Code challenge could be optional if PKCE flow is not used
+ if code != "" {
+ if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
+ log.Debug("Failed to set code state: ", err)
+ return res, err
+ }
+ }
+
go func() {
db.Provider.DeleteOTP(gc, otp)
if isSignUp {
diff --git a/server/token/auth_token.go b/server/token/auth_token.go
index 607f64d..32d1085 100644
--- a/server/token/auth_token.go
+++ b/server/token/auth_token.go
@@ -50,9 +50,6 @@ type SessionData struct {
// CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string, code string) (*Token, error) {
-
- fmt.Println("=> original nonce:", nonce)
-
hostname := parsers.GetHost(gc)
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
if err != nil {
@@ -72,7 +69,6 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l
codeHashString := ""
if code != "" {
- fmt.Println("=> atHash", atHashString)
codeHash := sha256.New()
codeHash.Write([]byte(code))
codeHashBytes := codeHash.Sum(nil)
@@ -80,7 +76,6 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l
codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest)
}
- fmt.Println("=> at hash nonce", nonce)
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod)
if err != nil {
return nil, err
@@ -116,7 +111,6 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string, l
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
}
- fmt.Printf("=> session data %+v\n", fingerPrintMap)
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
if err != nil {
@@ -381,8 +375,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH
claimKey: roles,
}
- fmt.Println("=> nonce", nonce)
-
// split nonce to see if its authorization code grant method
if cHash != "" {
@@ -393,8 +385,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH
customClaims["at_hash"] = atHash
}
- fmt.Println("custom_claims", customClaims)
-
for k, v := range userMap {
if k != "roles" {
customClaims[k] = v