fix: other auth recipes for oidc idp + remove logs

This commit is contained in:
Lakhan Samani 2022-11-15 21:45:08 +05:30
parent 579899c397
commit 75a547cfe2
12 changed files with 248 additions and 117 deletions

View File

@ -57,7 +57,6 @@ export default function Root({
urlProps.redirect_uri = urlProps.redirectURL; urlProps.redirect_uri = urlProps.redirectURL;
useEffect(() => { useEffect(() => {
console.log(config);
if (token) { if (token) {
let redirectURL = config.redirectURL || '/app'; 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}`; 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({
<Route path="/app" exact> <Route path="/app" exact>
<Login urlProps={urlProps} /> <Login urlProps={urlProps} />
</Route> </Route>
<Route path="/app/signup" exact> <Route path="/app/signup">
<SignUp urlProps={urlProps} /> <SignUp urlProps={urlProps} />
</Route> </Route>
<Route path="/app/reset-password"> <Route path="/app/reset-password">

View File

@ -2278,6 +2278,10 @@ input VerifyEmailInput {
input ResendVerifyEmailInput { input ResendVerifyEmailInput {
email: String! email: String!
identifier: 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 { input UpdateProfileInput {
@ -2425,10 +2429,18 @@ input DeleteEmailTemplateRequest {
input VerifyOTPRequest { input VerifyOTPRequest {
email: String! email: String!
otp: 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 { input ResendOTPRequest {
email: String! 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 { type Mutation {
@ -14679,7 +14691,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"email"} fieldsInOrder := [...]string{"email", "state"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -14694,6 +14706,14 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
if err != nil { if err != nil {
return it, err 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 asMap[k] = v
} }
fieldsInOrder := [...]string{"email", "identifier"} fieldsInOrder := [...]string{"email", "identifier", "state"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -14730,6 +14750,14 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con
if err != nil { if err != nil {
return it, err 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 asMap[k] = v
} }
fieldsInOrder := [...]string{"email", "otp"} fieldsInOrder := [...]string{"email", "otp", "state"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -15902,6 +15930,14 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
if err != nil { if err != nil {
return it, err 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
}
} }
} }

View File

@ -201,11 +201,13 @@ type PaginationInput struct {
type ResendOTPRequest struct { type ResendOTPRequest struct {
Email string `json:"email"` Email string `json:"email"`
State *string `json:"state"`
} }
type ResendVerifyEmailInput struct { type ResendVerifyEmailInput struct {
Email string `json:"email"` Email string `json:"email"`
Identifier string `json:"identifier"` Identifier string `json:"identifier"`
State *string `json:"state"`
} }
type ResetPasswordInput struct { type ResetPasswordInput struct {
@ -417,6 +419,7 @@ type VerifyEmailInput struct {
type VerifyOTPRequest struct { type VerifyOTPRequest struct {
Email string `json:"email"` Email string `json:"email"`
Otp string `json:"otp"` Otp string `json:"otp"`
State *string `json:"state"`
} }
type Webhook struct { type Webhook struct {

View File

@ -45,9 +45,7 @@ import (
"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/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/token" "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. // 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.
@ -110,17 +108,9 @@ func AuthorizeHandler() gin.HandlerFunc {
log := log.WithFields(log.Fields{ log := log.WithFields(log.Fields{
"response_mode": responseMode, "response_mode": responseMode,
"response_type": responseType, "response_type": responseType,
"state": state,
"code_challenge": codeChallenge,
"scope": scope,
"redirect_uri": redirectURI,
"nonce": nonce,
"code": code,
}) })
// memorystore.Provider.SetState(codeChallenge, code)
// TODO add state with timeout // TODO add state with timeout
// used for response mode query or fragment // used for response mode query or fragment
loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
if responseType == constants.ResponseTypeCode { if responseType == constants.ResponseTypeCode {
@ -141,17 +131,6 @@ func AuthorizeHandler() gin.HandlerFunc {
loginURL = "/app#" + loginState 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 == "" { if responseType == constants.ResponseTypeCode && codeChallenge == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
@ -275,7 +254,6 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken { if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken {
hostname := parsers.GetHost(gc)
// rollover the session for security // rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "") authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "")
if err != nil { if err != nil {
@ -299,7 +277,7 @@ func AuthorizeHandler() gin.HandlerFunc {
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
// used of query mode // 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{}{ res := map[string]interface{}{
"access_token": authToken.AccessToken.Token, "access_token": authToken.AccessToken.Token,
@ -308,19 +286,17 @@ func AuthorizeHandler() gin.HandlerFunc {
"scope": scope, "scope": scope,
"token_type": "Bearer", "token_type": "Bearer",
"expires_in": authToken.AccessToken.ExpiresAt, "expires_in": authToken.AccessToken.ExpiresAt,
"code": code,
} }
if utils.StringSliceContains(scope, "offline_access") { if nonce != "" {
refreshToken, _, err := token.CreateRefreshToken(user, claims.Roles, scope, hostname, nonce, claims.LoginMethod) params += "&nonce=" + nonce
if err != nil { res["nonce"] = nonce
log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
return
} }
res["refresh_token"] = refreshToken
params += "&refresh_token=" + refreshToken if authToken.RefreshToken != nil {
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+nonce, refreshToken) 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 { if responseMode == constants.ResponseModeQuery {
@ -349,6 +325,9 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error { 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 { if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken {
return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode) 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 return
case constants.ResponseModeFormPost: case constants.ResponseModeFormPost:
fmt.Println("=> trying tof orm post")
fmt.Printf("=> %+v \n", data["response"])
gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{ gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{
"target_origin": redirectURI, "target_origin": redirectURI,
"authorization_response": data["response"], "authorization_response": data["response"],

View File

@ -56,20 +56,20 @@ func OAuthCallbackHandler() gin.HandlerFunc {
scopes := strings.Split(sessionSplit[3], ",") scopes := strings.Split(sessionSplit[3], ",")
user := models.User{} user := models.User{}
code := ctx.Request.FormValue("code") oauthCode := ctx.Request.FormValue("code")
switch provider { switch provider {
case constants.AuthRecipeMethodGoogle: case constants.AuthRecipeMethodGoogle:
user, err = processGoogleUserInfo(code) user, err = processGoogleUserInfo(oauthCode)
case constants.AuthRecipeMethodGithub: case constants.AuthRecipeMethodGithub:
user, err = processGithubUserInfo(code) user, err = processGithubUserInfo(oauthCode)
case constants.AuthRecipeMethodFacebook: case constants.AuthRecipeMethodFacebook:
user, err = processFacebookUserInfo(code) user, err = processFacebookUserInfo(oauthCode)
case constants.AuthRecipeMethodLinkedIn: case constants.AuthRecipeMethodLinkedIn:
user, err = processLinkedInUserInfo(code) user, err = processLinkedInUserInfo(oauthCode)
case constants.AuthRecipeMethodApple: case constants.AuthRecipeMethodApple:
user, err = processAppleUserInfo(code) user, err = processAppleUserInfo(oauthCode)
case constants.AuthRecipeMethodTwitter: case constants.AuthRecipeMethodTwitter:
user, err = processTwitterUserInfo(code, sessionState) user, err = processTwitterUserInfo(oauthCode, sessionState)
default: default:
log.Info("Invalid oauth provider") log.Info("Invalid oauth provider")
err = fmt.Errorf(`invalid oauth provider`) err = fmt.Errorf(`invalid oauth provider`)
@ -200,19 +200,50 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// TODO // TODO
// use stateValue to get code / nonce // use stateValue to get code / nonce
// add code / nonce to id_token // add code / nonce to id_token
nonce := uuid.New().String() code := ""
authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, "") 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 { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
ctx.JSON(500, gin.H{"error": err.Error()}) 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() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
expiresIn = 1 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 sessionKey := provider + ":" + user.ID
cookie.SetSession(ctx, authToken.FingerPrintHash) cookie.SetSession(ctx, authToken.FingerPrintHash)
@ -220,7 +251,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
if authToken.RefreshToken != nil { 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) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
} }

View File

@ -3,7 +3,6 @@ package handlers
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -33,10 +32,6 @@ type RequestBody struct {
// grant type required // grant type required
func TokenHandler() gin.HandlerFunc { func TokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) { 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 var reqBody RequestBody
if err := gc.Bind(&reqBody); err != nil { if err := gc.Bind(&reqBody); err != nil {
log.Debug("Error binding JSON: ", err) log.Debug("Error binding JSON: ", err)
@ -47,8 +42,6 @@ func TokenHandler() gin.HandlerFunc {
return return
} }
fmt.Printf("=>req body: %+v\n", reqBody)
codeVerifier := strings.TrimSpace(reqBody.CodeVerifier) codeVerifier := strings.TrimSpace(reqBody.CodeVerifier)
code := strings.TrimSpace(reqBody.Code) code := strings.TrimSpace(reqBody.Code)
clientID := strings.TrimSpace(reqBody.ClientID) clientID := strings.TrimSpace(reqBody.ClientID)
@ -125,7 +118,6 @@ func TokenHandler() gin.HandlerFunc {
// [0] -> code_challenge // [0] -> code_challenge
// [1] -> session cookie // [1] -> session cookie
sessionDataSplit := strings.Split(sessionData, "@@") sessionDataSplit := strings.Split(sessionData, "@@")
fmt.Println("=> sessionDataSplit:", sessionDataSplit)
go memorystore.Provider.RemoveState(code) 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(base64.RawURLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_") encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "") encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
fmt.Println("=> encryptedCode", encryptedCode)
if encryptedCode != sessionDataSplit[0] { if encryptedCode != sessionDataSplit[0] {
gc.JSON(http.StatusBadRequest, gin.H{ gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier", "error": "invalid_code_verifier",
@ -166,8 +157,6 @@ func TokenHandler() gin.HandlerFunc {
return return
} }
fmt.Printf("=>claims: %+v\n", &claims)
userID = claims.Subject userID = claims.Subject
roles = claims.Roles roles = claims.Roles
scope = claims.Scope scope = claims.Scope
@ -242,10 +231,6 @@ func TokenHandler() gin.HandlerFunc {
} }
nonce := uuid.New().String() + "@@" + code nonce := uuid.New().String() + "@@" + code
fmt.Println("=> code", code)
fmt.Println("=> nonce", nonce)
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code) authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
if err != nil { if err != nil {
log.Debug("Error creating auth token: ", err) log.Debug("Error creating auth token: ", err)

View File

@ -100,8 +100,29 @@ func VerifyEmailHandler() gin.HandlerFunc {
loginMethod = constants.AuthRecipeMethodMagicLinkLogin loginMethod = constants.AuthRecipeMethodMagicLinkLogin
} }
nonce := uuid.New().String() code := ""
authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, "") // 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 { if err != nil {
log.Debug("Error creating auth token: ", err) log.Debug("Error creating auth token: ", err)
errorRes["error_description"] = err.Error() errorRes["error_description"] = err.Error()
@ -109,12 +130,27 @@ func VerifyEmailHandler() gin.HandlerFunc {
return 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() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
expiresIn = 1 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 sessionKey := loginMethod + ":" + user.ID
cookie.SetSession(c, authToken.FingerPrintHash) cookie.SetSession(c, authToken.FingerPrintHash)

View File

@ -141,10 +141,9 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
}, nil }, nil
} }
nonce := uuid.New().String()
fmt.Println("=> state", refs.StringValue(params.State))
code := "" code := ""
codeChallenge := "" codeChallenge := ""
nonce := ""
if params.State != nil { if params.State != nil {
// Get state from store // Get state from store
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State)) 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 { if len(authorizeStateSplit) > 1 {
code = authorizeStateSplit[0] code = authorizeStateSplit[0]
codeChallenge = authorizeStateSplit[1] codeChallenge = authorizeStateSplit[1]
fmt.Println("=> code info", authorizeStateSplit)
} else { } else {
nonce = authorizeState 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) authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
if err != nil { if err != nil {
log.Debug("Failed to create auth token", err) log.Debug("Failed to create auth token", err)
return res, 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() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
expiresIn = 1 expiresIn = 1
@ -185,15 +195,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID sessionStoreKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionStoreKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) 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 { if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token res.RefreshToken = &authToken.RefreshToken.Token

View File

@ -34,16 +34,6 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err 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) isSignupDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableSignUp)
if err != nil { if err != nil {
log.Debug("Error getting signup disabled: ", err) log.Debug("Error getting signup disabled: ", err)
@ -253,9 +243,26 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
scope = params.Scope scope = params.Scope
} }
nonce := uuid.New().String() code := ""
if code != "" { codeChallenge := ""
nonce = nonce + "@@" + code 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) 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 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() expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
if expiresIn <= 0 { if expiresIn <= 0 {
expiresIn = 1 expiresIn = 1

View File

@ -16,6 +16,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/parsers" "github.com/authorizerdev/authorizer/server/parsers"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
@ -85,13 +86,42 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"} scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String() code := ""
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") // 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 { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
return res, 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() { go func() {
if isSignUp { if isSignUp {
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user) utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user)

View File

@ -12,6 +12,7 @@ import (
"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"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid" "github.com/google/uuid"
@ -58,13 +59,40 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"} scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String() code := ""
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, "") 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 { if err != nil {
log.Debug("Failed to create auth token: ", err) log.Debug("Failed to create auth token: ", err)
return res, 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() { go func() {
db.Provider.DeleteOTP(gc, otp) db.Provider.DeleteOTP(gc, otp)
if isSignUp { if isSignUp {

View File

@ -50,9 +50,6 @@ type SessionData struct {
// CreateAuthToken creates a new auth token when userlogs in // 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) { 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) hostname := parsers.GetHost(gc)
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod) _, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
if err != nil { if err != nil {
@ -72,7 +69,6 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l
codeHashString := "" codeHashString := ""
if code != "" { if code != "" {
fmt.Println("=> atHash", atHashString)
codeHash := sha256.New() codeHash := sha256.New()
codeHash.Write([]byte(code)) codeHash.Write([]byte(code))
codeHashBytes := codeHash.Sum(nil) 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) codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest)
} }
fmt.Println("=> at hash nonce", nonce)
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod) idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,7 +111,6 @@ func CreateSessionToken(user models.User, nonce string, roles, scope []string, l
IssuedAt: time.Now().Unix(), IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(), ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
} }
fmt.Printf("=> session data %+v\n", fingerPrintMap)
fingerPrintBytes, _ := json.Marshal(fingerPrintMap) fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes)) fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
if err != nil { if err != nil {
@ -381,8 +375,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH
claimKey: roles, claimKey: roles,
} }
fmt.Println("=> nonce", nonce)
// split nonce to see if its authorization code grant method // split nonce to see if its authorization code grant method
if cHash != "" { if cHash != "" {
@ -393,8 +385,6 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cH
customClaims["at_hash"] = atHash customClaims["at_hash"] = atHash
} }
fmt.Println("custom_claims", customClaims)
for k, v := range userMap { for k, v := range userMap {
if k != "roles" { if k != "roles" {
customClaims[k] = v customClaims[k] = v