From 75a547cfe2f55a5a0907bd4166560175a0e81f2a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 15 Nov 2022 21:45:08 +0530 Subject: [PATCH] fix: other auth recipes for oidc idp + remove logs --- app/src/Root.tsx | 3 +- server/graph/generated/generated.go | 42 +++++++++++++++++++++-- server/graph/model/models_gen.go | 13 ++++--- server/handlers/authorize.go | 53 ++++++++--------------------- server/handlers/oauth_callback.go | 53 +++++++++++++++++++++++------ server/handlers/token.go | 15 -------- server/handlers/verify_email.go | 42 +++++++++++++++++++++-- server/resolvers/login.go | 27 ++++++++------- server/resolvers/signup.go | 41 +++++++++++++++------- server/resolvers/verify_email.go | 34 ++++++++++++++++-- server/resolvers/verify_otp.go | 32 +++++++++++++++-- server/token/auth_token.go | 10 ------ 12 files changed, 248 insertions(+), 117 deletions(-) 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