feat: add token endpoint

This commit is contained in:
Lakhan Samani 2022-03-04 12:56:11 +05:30
parent 2946428ab8
commit 0787a3b494
4 changed files with 159 additions and 14 deletions

View File

@ -2,6 +2,7 @@ package cookie
import ( import (
"net/http" "net/http"
"net/url"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
@ -56,5 +57,9 @@ func GetSession(gc *gin.Context) (string, error) {
} }
} }
return cookie.Value, nil decodedValue, err := url.PathUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
} }

View File

@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
@ -27,6 +26,8 @@ func AuthorizeHandler() gin.HandlerFunc {
responseType := strings.TrimSpace(gc.Query("response_type")) responseType := strings.TrimSpace(gc.Query("response_type"))
state := strings.TrimSpace(gc.Query("state")) state := strings.TrimSpace(gc.Query("state"))
codeChallenge := strings.TrimSpace(gc.Query("code_challenge")) codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
scopeString := strings.TrimSpace(gc.Query("scope"))
scope := []string{}
template := "authorize.tmpl" template := "authorize.tmpl"
if redirectURI == "" { if redirectURI == "" {
@ -59,6 +60,10 @@ func AuthorizeHandler() gin.HandlerFunc {
responseType = "token" responseType = "token"
} }
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
}
isResponseTypeCode := responseType == "code" isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token" isResponseTypeToken := responseType == "token"
@ -142,7 +147,7 @@ func AuthorizeHandler() gin.HandlerFunc {
// rollover the session for security // rollover the session for security
sessionstore.RemoveState(sessionToken) sessionstore.RemoveState(sessionToken)
nonce := uuid.New().String() nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, claims.Scope) newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ gc.HTML(http.StatusOK, template, gin.H{
"target_origin": nil, "target_origin": nil,
@ -160,7 +165,7 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID) sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
cookie.SetSession(gc, newSessionToken) cookie.SetSession(gc, newSessionToken)
code := uuid.New().String() code := uuid.New().String()
sessionstore.SetState("code_challenge_"+codeChallenge, code) sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
gc.HTML(http.StatusOK, template, gin.H{ gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI, "target_origin": redirectURI,
"authorization_response": map[string]string{ "authorization_response": map[string]string{
@ -173,7 +178,7 @@ func AuthorizeHandler() gin.HandlerFunc {
if isResponseTypeToken { if isResponseTypeToken {
// rollover the session for security // rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope) authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
if err != nil { if err != nil {
gc.HTML(http.StatusOK, template, gin.H{ gc.HTML(http.StatusOK, template, gin.H{
"target_origin": nil, "target_origin": nil,
@ -191,20 +196,28 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800) expiresIn := int64(1800)
gc.HTML(http.StatusOK, template, gin.H{ res := map[string]interface{}{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"access_token": authToken.AccessToken.Token, "access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token, "id_token": authToken.IDToken.Token,
"state": state, "state": state,
"scope": claims.Scope, "scope": scope,
"token_type": "Bearer",
"expires_in": expiresIn, "expires_in": expiresIn,
}, }
if authToken.RefreshToken != nil {
res["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": res,
}) })
return return
} }
fmt.Println("=> returning from here...")
// by default return with error // by default return with error
gc.HTML(http.StatusOK, template, gin.H{ gc.HTML(http.StatusOK, template, gin.H{

126
server/handlers/token.go Normal file
View File

@ -0,0 +1,126 @@
package handlers
import (
"crypto/sha256"
"encoding/base64"
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
)
func TokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
var reqBody map[string]string
if err := gc.BindJSON(&reqBody); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "error_binding_json",
"error_description": err.Error(),
})
return
}
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
code := strings.TrimSpace(reqBody["code"])
redirectURI := strings.TrimSpace(reqBody["redirect_uri"])
if codeVerifier == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is required",
})
return
}
if code == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code",
"error_description": "The code is required",
})
return
}
if redirectURI == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_redirect_uri",
"error_description": "The redirect URI is required",
})
return
}
hash := sha256.New()
hash.Write([]byte(codeVerifier))
encryptedCode := strings.TrimSuffix(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "=")
sessionData := sessionstore.GetState(encryptedCode)
if sessionData == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// split session data
// it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@")
if sessionDataSplit[0] != code {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// validate session
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "Invalid session data",
})
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
// rollover the session for security
sessionstore.RemoveState(sessionDataSplit[1])
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
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)
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"scope": claims.Scope,
"expires_in": expiresIn,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.JSON(http.StatusOK, res)
}
}

View File

@ -26,6 +26,7 @@ func InitRouter() *gin.Engine {
router.GET("/authorize", handlers.AuthorizeHandler()) router.GET("/authorize", handlers.AuthorizeHandler())
router.GET("/userinfo", handlers.UserInfoHandler()) router.GET("/userinfo", handlers.UserInfoHandler())
router.GET("/logout", handlers.LogoutHandler()) router.GET("/logout", handlers.LogoutHandler())
router.POST("/token", handlers.TokenHandler())
router.LoadHTMLGlob("templates/*") router.LoadHTMLGlob("templates/*")
// login page app related routes. // login page app related routes.