Feat: add screen_hint param in /authorize api for explicit signup redirection (#420)

* Feat:
- Introduce screen_hint param in /authorize for explicit signup redirection

* Feat:
- Declare variable for base path and signup path
- Add social login on signup page

* Refactor:
- Update variable name for screen hint param
This commit is contained in:
scaletech-milan 2023-11-21 13:08:32 +05:30 committed by GitHub
parent fe4c693324
commit de5c18b60f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 23 deletions

View File

@ -1,5 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { AuthorizerSignup } from '@authorizerdev/authorizer-react'; import { AuthorizerSignup, AuthorizerSocialLogin } from '@authorizerdev/authorizer-react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -19,6 +19,7 @@ export default function SignUp({
<Fragment> <Fragment>
<h1 style={{ textAlign: 'center' }}>Sign Up</h1> <h1 style={{ textAlign: 'center' }}>Sign Up</h1>
<br /> <br />
<AuthorizerSocialLogin urlProps={urlProps} />
<AuthorizerSignup urlProps={urlProps} /> <AuthorizerSignup urlProps={urlProps} />
<FooterContent> <FooterContent>
Already have an account? <Link to="/app"> Login</Link> Already have an account? <Link to="/app"> Login</Link>

View File

@ -16,4 +16,7 @@ const (
ResponseTypeToken = "token" ResponseTypeToken = "token"
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token. // For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
ResponseTypeIDToken = "id_token" ResponseTypeIDToken = "id_token"
// Constant indicating the "signup" screen hint for customizing authentication process and redirect to a signup page.
ScreenHintSignUp = "signup"
) )

View File

@ -55,6 +55,8 @@ import (
const ( const (
authorizeWebMessageTemplate = "authorize_web_message.tmpl" authorizeWebMessageTemplate = "authorize_web_message.tmpl"
authorizeFormPostTemplate = "authorize_form_post.tmpl" authorizeFormPostTemplate = "authorize_form_post.tmpl"
baseAppPath = "/app"
signupPath = "/app/signup"
) )
// AuthorizeHandler is the handler for the /authorize route // AuthorizeHandler is the handler for the /authorize route
@ -74,6 +76,7 @@ func AuthorizeHandler() gin.HandlerFunc {
clientID := strings.TrimSpace(gc.Query("client_id")) clientID := strings.TrimSpace(gc.Query("client_id"))
responseMode := strings.TrimSpace(gc.Query("response_mode")) responseMode := strings.TrimSpace(gc.Query("response_mode"))
nonce := strings.TrimSpace(gc.Query("nonce")) nonce := strings.TrimSpace(gc.Query("nonce"))
screenHint := strings.TrimSpace(gc.Query("screen_hint"))
var scope []string var scope []string
if scopeString == "" { if scopeString == "" {
@ -120,27 +123,33 @@ func AuthorizeHandler() gin.HandlerFunc {
// 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 authState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
if responseType == constants.ResponseTypeCode { if responseType == constants.ResponseTypeCode {
loginState += "&code=" + code authState += "&code=" + code
if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil { if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil {
log.Debug("Error setting temp code", err) log.Debug("Error setting temp code", err)
} }
} else { } else {
loginState += "&nonce=" + nonce authState += "&nonce=" + nonce
if err := memorystore.Provider.SetState(state, nonce); err != nil { if err := memorystore.Provider.SetState(state, nonce); err != nil {
log.Debug("Error setting temp code", err) log.Debug("Error setting temp code", err)
} }
} }
loginURL := "/app?" + loginState authURL := baseAppPath + "?" + authState
if responseMode == constants.ResponseModeFragment { if screenHint == constants.ScreenHintSignUp {
loginURL = "/app#" + loginState authURL = signupPath + "?" + authState
}
if responseMode == constants.ResponseModeFragment && screenHint == constants.ScreenHintSignUp {
authURL = signupPath + "#" + authState
} else if responseMode == constants.ResponseModeFragment {
authURL = baseAppPath + "#" + authState
} }
if responseType == constants.ResponseTypeCode && codeChallenge == "" { if responseType == constants.ResponseTypeCode && codeChallenge == "" {
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"error": "code_challenge_required", "error": "code_challenge_required",
@ -160,7 +169,7 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionToken, err := cookie.GetSession(gc) sessionToken, err := cookie.GetSession(gc)
if err != nil { if err != nil {
log.Debug("GetSession failed: ", err) log.Debug("GetSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -168,7 +177,7 @@ func AuthorizeHandler() gin.HandlerFunc {
claims, err := token.ValidateBrowserSession(gc, sessionToken) claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil { if err != nil {
log.Debug("ValidateBrowserSession failed: ", err) log.Debug("ValidateBrowserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -176,7 +185,7 @@ func AuthorizeHandler() gin.HandlerFunc {
user, err := db.Provider.GetUserByID(gc, userID) user, err := db.Provider.GetUserByID(gc, userID)
if err != nil { if err != nil {
log.Debug("GetUserByID failed: ", err) log.Debug("GetUserByID failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"error": "signup_required", "error": "signup_required",
@ -197,27 +206,27 @@ func AuthorizeHandler() gin.HandlerFunc {
newSessionTokenData, newSessionToken, newSessionExpiresAt, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod) newSessionTokenData, newSessionToken, newSessionExpiresAt, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
if err != nil { if err != nil {
log.Debug("CreateSessionToken failed: ", err) log.Debug("CreateSessionToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
// TODO: add state with timeout // TODO: add state with timeout
// if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil { // if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil {
// log.Debug("SetState failed: ", err) // log.Debug("SetState failed: ", err)
// handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) // handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
// return // return
// } // }
// TODO: add state with timeout // TODO: add state with timeout
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil { if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil {
log.Debug("SetState failed: ", err) log.Debug("SetState failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken, newSessionExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken, newSessionExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -251,7 +260,7 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
} }
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": map[string]interface{}{ "response": map[string]interface{}{
"code": code, "code": code,
@ -267,19 +276,19 @@ func AuthorizeHandler() gin.HandlerFunc {
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 {
log.Debug("CreateAuthToken failed: ", err) log.Debug("CreateAuthToken failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash, authToken.SessionTokenExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt); err != nil { if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.AccessToken.Token, authToken.AccessToken.ExpiresAt); err != nil {
log.Debug("SetUserSession failed: ", err) log.Debug("SetUserSession failed: ", err)
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
return return
} }
@ -322,14 +331,14 @@ func AuthorizeHandler() gin.HandlerFunc {
} }
} }
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{ handleResponse(gc, responseMode, authURL, redirectURI, map[string]interface{}{
"type": "authorization_response", "type": "authorization_response",
"response": res, "response": res,
}, http.StatusOK) }, http.StatusOK)
return return
} }
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK) handleResponse(gc, responseMode, authURL, redirectURI, loginError, http.StatusOK)
} }
} }
@ -352,14 +361,14 @@ func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeC
return nil return nil
} }
func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) { func handleResponse(gc *gin.Context, responseMode, authURI, redirectURI string, data map[string]interface{}, httpStatusCode int) {
isAuthenticationRequired := false isAuthenticationRequired := false
if _, ok := data["response"].(map[string]interface{})["error"]; ok { if _, ok := data["response"].(map[string]interface{})["error"]; ok {
isAuthenticationRequired = true isAuthenticationRequired = true
} }
if isAuthenticationRequired && responseMode != constants.ResponseModeWebMessage { if isAuthenticationRequired && responseMode != constants.ResponseModeWebMessage {
gc.Redirect(http.StatusFound, loginURI) gc.Redirect(http.StatusFound, authURI)
return return
} }