fix: add token information in redirect url

This commit is contained in:
Lakhan Samani
2022-03-08 12:36:26 +05:30
parent 57bc091499
commit 8c2bf6ee0d
26 changed files with 440 additions and 225 deletions

View File

@@ -1,13 +1,11 @@
package handlers
import (
"encoding/json"
"log"
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
@@ -18,7 +16,6 @@ import (
type State struct {
AuthorizerURL string `json:"authorizerURL"`
RedirectURL string `json:"redirectURL"`
State string `json:"state"`
}
// AppHandler is the handler for the /app route
@@ -30,44 +27,25 @@ func AppHandler() gin.HandlerFunc {
return
}
state := c.Query("state")
redirect_uri := strings.TrimSpace(c.Query("redirect_uri"))
state := strings.TrimSpace(c.Query("state"))
scopeString := strings.TrimSpace(c.Query("scope"))
var stateObj State
if state == "" {
stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = hostname + "/app"
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
decodedState, err := crypto.DecryptB64(state)
if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return
}
err = json.Unmarshal([]byte(decodedState), &stateObj)
if err != nil {
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
return
}
stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/")
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
scope = strings.Split(scopeString, " ")
}
if redirect_uri == "" {
redirect_uri = hostname + "/app"
} else {
// validate redirect url with allowed origins
if !utils.IsValidOrigin(stateObj.RedirectURL) {
if !utils.IsValidOrigin(redirect_uri) {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
if stateObj.AuthorizerURL == "" {
c.JSON(400, gin.H{"error": "invalid authorizer url"})
return
}
// validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
c.JSON(400, gin.H{"error": "invalid host url"})
return
}
}
// debug the request state
@@ -78,10 +56,11 @@ func AppHandler() gin.HandlerFunc {
}
}
c.HTML(http.StatusOK, "app.tmpl", gin.H{
"data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL,
"state": stateObj.State,
"data": map[string]interface{}{
"authorizerURL": hostname,
"redirectURL": redirect_uri,
"scope": scope,
"state": state,
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
},

View File

@@ -2,16 +2,15 @@ package handlers
import (
"net/http"
"strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
@@ -36,6 +35,13 @@ func AuthorizeHandler() gin.HandlerFunc {
template := "authorize.tmpl"
responseMode := strings.TrimSpace(gc.Query("response_mode"))
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
if responseMode == "" {
responseMode = "query"
}
@@ -50,9 +56,7 @@ func AuthorizeHandler() gin.HandlerFunc {
isQuery := responseMode == "query"
hostname := utils.GetHost(gc)
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`)
loginURL := "/app?state=" + loginRedirectState
loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
if clientID == "" {
if isQuery {
@@ -109,13 +113,6 @@ func AuthorizeHandler() gin.HandlerFunc {
responseType = "token"
}
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token"
@@ -279,8 +276,11 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
// used of query mode
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
@@ -292,16 +292,25 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": res,
},
})
if isQuery {
if strings.Contains(redirectURI, "?") {
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
} else {
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
}
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": res,
},
})
}
return
}

View File

@@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
"time"
@@ -21,7 +22,6 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/oauth2"
)
@@ -37,16 +37,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
}
sessionstore.GetState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
sessionSplit := strings.Split(state, "@")
// TODO validate redirect url
if len(sessionSplit) < 2 {
if len(sessionSplit) < 3 {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
inputRoles := strings.Split(sessionSplit[2], ",")
stateValue := sessionSplit[0]
redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",")
scopes := strings.Split(sessionSplit[3], ",")
var err error
user := models.User{}
@@ -145,17 +146,29 @@ func OAuthCallbackHandler() gin.HandlerFunc {
}
}
// TODO use query param
scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope)
authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
}
expiresIn := int64(1800)
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
sessionstore.SetState(newSessionToken, nonce+"@"+user.ID)
cookie.SetSession(c, newSessionToken)
go utils.SaveSessionInDB(c, user.ID)
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
}
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}

View File

@@ -10,23 +10,38 @@ import (
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) {
hostname := utils.GetHost(c)
redirectURL := c.Query("redirectURL")
roles := c.Query("roles")
redirectURI := strings.TrimSpace(c.Query("redirectURL"))
roles := strings.TrimSpace(c.Query("roles"))
state := strings.TrimSpace(c.Query("state"))
scopeString := strings.TrimSpace(c.Query("scope"))
if redirectURL == "" {
if redirectURI == "" {
c.JSON(400, gin.H{
"error": "invalid redirect url",
"error": "invalid redirect uri",
})
return
}
if state == "" {
c.JSON(400, gin.H{
"error": "invalid state",
})
return
}
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
if roles != "" {
// validate role
rolesSplit := strings.Split(roles, ",")
@@ -43,8 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
uuid := uuid.New()
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",")
provider := c.Param("oauth_provider")
isProviderConfigured := true

View File

@@ -2,6 +2,7 @@ package handlers
import (
"net/http"
"strconv"
"strings"
"time"
@@ -11,7 +12,6 @@ import (
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// VerifyEmailHandler handles the verify email route.
@@ -19,7 +19,7 @@ import (
func VerifyEmailHandler() gin.HandlerFunc {
return func(c *gin.Context) {
errorRes := gin.H{
"error": "invalid token",
"error": "invalid_token",
}
tokenInQuery := c.Query("token")
if tokenInQuery == "" {
@@ -29,30 +29,24 @@ func VerifyEmailHandler() gin.HandlerFunc {
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
if err != nil {
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
// verify if token exists in db
hostname := utils.GetHost(c)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
if err != nil {
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil {
c.JSON(400, gin.H{
"message": err.Error(),
})
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes)
return
}
@@ -65,21 +59,53 @@ func VerifyEmailHandler() gin.HandlerFunc {
// delete from verification table
db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope)
state := strings.TrimSpace(c.Query("state"))
redirectURL := strings.TrimSpace(c.Query("redirect_uri"))
rolesString := strings.TrimSpace(c.Query("roles"))
var roles []string
if rolesString == "" {
roles = strings.Split(user.Roles, ",")
} else {
roles = strings.Split(rolesString, ",")
}
scopeString := strings.TrimSpace(c.Query("scope"))
var scope []string
if scopeString == "" {
scope = []string{"openid", "email", "profile"}
} else {
scope = strings.Split(scopeString, " ")
}
authToken, err := token.CreateAuthToken(c, user, roles, scope)
if err != nil {
c.JSON(400, gin.H{
"message": err.Error(),
})
errorRes["error_description"] = err.Error()
c.JSON(500, errorRes)
return
}
sessionstore.SetState(authToken, nonce+"@"+user.ID)
cookie.SetSession(c, authToken)
expiresIn := int64(1800)
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
if redirectURL == "" {
redirectURL = claim["redirect_url"].(string)
}
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
}
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}