Skip to content

Commit 0787a3b

Browse files
committed
feat: add token endpoint
1 parent 2946428 commit 0787a3b

File tree

4 files changed

+159
-14
lines changed

4 files changed

+159
-14
lines changed

server/cookie/cookie.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cookie
22

33
import (
44
"net/http"
5+
"net/url"
56

67
"github.com/authorizerdev/authorizer/server/constants"
78
"github.com/authorizerdev/authorizer/server/envstore"
@@ -56,5 +57,9 @@ func GetSession(gc *gin.Context) (string, error) {
5657
}
5758
}
5859

59-
return cookie.Value, nil
60+
decodedValue, err := url.PathUnescape(cookie.Value)
61+
if err != nil {
62+
return "", err
63+
}
64+
return decodedValue, nil
6065
}

server/handlers/authorize.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package handlers
22

33
import (
4-
"fmt"
54
"net/http"
65
"strings"
76

@@ -27,6 +26,8 @@ func AuthorizeHandler() gin.HandlerFunc {
2726
responseType := strings.TrimSpace(gc.Query("response_type"))
2827
state := strings.TrimSpace(gc.Query("state"))
2928
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
29+
scopeString := strings.TrimSpace(gc.Query("scope"))
30+
scope := []string{}
3031
template := "authorize.tmpl"
3132

3233
if redirectURI == "" {
@@ -59,6 +60,10 @@ func AuthorizeHandler() gin.HandlerFunc {
5960
responseType = "token"
6061
}
6162

63+
if scopeString == "" {
64+
scope = []string{"openid", "profile", "email"}
65+
}
66+
6267
isResponseTypeCode := responseType == "code"
6368
isResponseTypeToken := responseType == "token"
6469

@@ -142,7 +147,7 @@ func AuthorizeHandler() gin.HandlerFunc {
142147
// rollover the session for security
143148
sessionstore.RemoveState(sessionToken)
144149
nonce := uuid.New().String()
145-
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, claims.Scope)
150+
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
146151
if err != nil {
147152
gc.HTML(http.StatusOK, template, gin.H{
148153
"target_origin": nil,
@@ -160,7 +165,7 @@ func AuthorizeHandler() gin.HandlerFunc {
160165
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
161166
cookie.SetSession(gc, newSessionToken)
162167
code := uuid.New().String()
163-
sessionstore.SetState("code_challenge_"+codeChallenge, code)
168+
sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
164169
gc.HTML(http.StatusOK, template, gin.H{
165170
"target_origin": redirectURI,
166171
"authorization_response": map[string]string{
@@ -173,7 +178,7 @@ func AuthorizeHandler() gin.HandlerFunc {
173178

174179
if isResponseTypeToken {
175180
// rollover the session for security
176-
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
181+
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
177182
if err != nil {
178183
gc.HTML(http.StatusOK, template, gin.H{
179184
"target_origin": nil,
@@ -191,20 +196,28 @@ func AuthorizeHandler() gin.HandlerFunc {
191196
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
192197
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
193198
cookie.SetSession(gc, authToken.FingerPrintHash)
199+
194200
expiresIn := int64(1800)
201+
res := map[string]interface{}{
202+
"access_token": authToken.AccessToken.Token,
203+
"id_token": authToken.IDToken.Token,
204+
"state": state,
205+
"scope": scope,
206+
"token_type": "Bearer",
207+
"expires_in": expiresIn,
208+
}
209+
210+
if authToken.RefreshToken != nil {
211+
res["refresh_token"] = authToken.RefreshToken.Token
212+
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
213+
}
214+
195215
gc.HTML(http.StatusOK, template, gin.H{
196-
"target_origin": redirectURI,
197-
"authorization_response": map[string]interface{}{
198-
"access_token": authToken.AccessToken.Token,
199-
"id_token": authToken.IDToken.Token,
200-
"state": state,
201-
"scope": claims.Scope,
202-
"expires_in": expiresIn,
203-
},
216+
"target_origin": redirectURI,
217+
"authorization_response": res,
204218
})
205219
return
206220
}
207-
fmt.Println("=> returning from here...")
208221

209222
// by default return with error
210223
gc.HTML(http.StatusOK, template, gin.H{

server/handlers/token.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package handlers
2+
3+
import (
4+
"crypto/sha256"
5+
"encoding/base64"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/authorizerdev/authorizer/server/cookie"
10+
"github.com/authorizerdev/authorizer/server/db"
11+
"github.com/authorizerdev/authorizer/server/sessionstore"
12+
"github.com/authorizerdev/authorizer/server/token"
13+
"github.com/gin-gonic/gin"
14+
)
15+
16+
func TokenHandler() gin.HandlerFunc {
17+
return func(gc *gin.Context) {
18+
var reqBody map[string]string
19+
if err := gc.BindJSON(&reqBody); err != nil {
20+
gc.JSON(http.StatusBadRequest, gin.H{
21+
"error": "error_binding_json",
22+
"error_description": err.Error(),
23+
})
24+
return
25+
}
26+
27+
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
28+
code := strings.TrimSpace(reqBody["code"])
29+
redirectURI := strings.TrimSpace(reqBody["redirect_uri"])
30+
31+
if codeVerifier == "" {
32+
gc.JSON(http.StatusBadRequest, gin.H{
33+
"error": "invalid_code_verifier",
34+
"error_description": "The code verifier is required",
35+
})
36+
return
37+
}
38+
39+
if code == "" {
40+
gc.JSON(http.StatusBadRequest, gin.H{
41+
"error": "invalid_code",
42+
"error_description": "The code is required",
43+
})
44+
return
45+
}
46+
47+
if redirectURI == "" {
48+
gc.JSON(http.StatusBadRequest, gin.H{
49+
"error": "invalid_redirect_uri",
50+
"error_description": "The redirect URI is required",
51+
})
52+
return
53+
}
54+
55+
hash := sha256.New()
56+
hash.Write([]byte(codeVerifier))
57+
encryptedCode := strings.TrimSuffix(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "=")
58+
sessionData := sessionstore.GetState(encryptedCode)
59+
if sessionData == "" {
60+
gc.JSON(http.StatusBadRequest, gin.H{
61+
"error": "invalid_code_verifier",
62+
"error_description": "The code verifier is invalid",
63+
})
64+
return
65+
}
66+
67+
// split session data
68+
// it contains code@sessiontoken
69+
sessionDataSplit := strings.Split(sessionData, "@")
70+
71+
if sessionDataSplit[0] != code {
72+
gc.JSON(http.StatusBadRequest, gin.H{
73+
"error": "invalid_code_verifier",
74+
"error_description": "The code verifier is invalid",
75+
})
76+
return
77+
}
78+
79+
// validate session
80+
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
81+
if err != nil {
82+
gc.JSON(http.StatusUnauthorized, gin.H{
83+
"error": "unauthorized",
84+
"error_description": "Invalid session data",
85+
})
86+
return
87+
}
88+
userID := claims.Subject
89+
user, err := db.Provider.GetUserByID(userID)
90+
if err != nil {
91+
gc.JSON(http.StatusUnauthorized, gin.H{
92+
"error": "unauthorized",
93+
"error_description": "User not found",
94+
})
95+
return
96+
}
97+
// rollover the session for security
98+
sessionstore.RemoveState(sessionDataSplit[1])
99+
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
100+
if err != nil {
101+
gc.JSON(http.StatusUnauthorized, gin.H{
102+
"error": "unauthorized",
103+
"error_description": "User not found",
104+
})
105+
return
106+
}
107+
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
108+
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
109+
cookie.SetSession(gc, authToken.FingerPrintHash)
110+
111+
expiresIn := int64(1800)
112+
res := map[string]interface{}{
113+
"access_token": authToken.AccessToken.Token,
114+
"id_token": authToken.IDToken.Token,
115+
"scope": claims.Scope,
116+
"expires_in": expiresIn,
117+
}
118+
119+
if authToken.RefreshToken != nil {
120+
res["refresh_token"] = authToken.RefreshToken.Token
121+
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
122+
}
123+
124+
gc.JSON(http.StatusOK, res)
125+
}
126+
}

server/routes/routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func InitRouter() *gin.Engine {
2626
router.GET("/authorize", handlers.AuthorizeHandler())
2727
router.GET("/userinfo", handlers.UserInfoHandler())
2828
router.GET("/logout", handlers.LogoutHandler())
29+
router.POST("/token", handlers.TokenHandler())
2930

3031
router.LoadHTMLGlob("templates/*")
3132
// login page app related routes.

0 commit comments

Comments
 (0)