package handlers import ( "fmt" "net/http" "os" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/logto-io/go/client" ) // SessionStorage implements Logto's Storage interface using Gin sessions // Avoid cookie-based sessions due to size limits; use memory-based sessions for demo // In production, use Redis/MongoDB type SessionStorage struct { session sessions.Session } func (s *SessionStorage) GetItem(key string) string { value := s.session.Get(key) if value == nil { return "" } str, ok := value.(string) if !ok { return "" } return str } func (s *SessionStorage) SetItem(key, value string) { s.session.Set(key, value) s.session.Save() } // getLogtoConfig returns Logto config from environment variables func getLogtoConfig() *client.LogtoConfig { return &client.LogtoConfig{ Endpoint: os.Getenv("LOGTO_ENDPOINT"), AppId: os.Getenv("LOGTO_APP_ID"), AppSecret: os.Getenv("LOGTO_APP_SECRET"), } } // HomeHandler shows auth state and sign-in/sign-out links // Note: The /service/auth endpoint is for debugging purposes only func HomeHandler(ctx *gin.Context) { session := sessions.Default(ctx) logtoClient := client.NewLogtoClient(getLogtoConfig(), &SessionStorage{session: session}) var debugInfo string debugInfo = "

Logto Auth Debugging Page

" // Basic auth state if logtoClient.IsAuthenticated() { debugInfo += "
Authentication Status: Logged In ✓
" // Get ID Token claims idTokenClaims, err := logtoClient.GetIdTokenClaims() if err != nil { debugInfo += "
Error fetching ID token claims: " + err.Error() + "
" } else { debugInfo += "

ID Token Claims

" debugInfo += "
"
			
			// Display claims in a simpler format
			debugInfo += ""
			debugInfo += ""
			
			// Display the raw claims directly without any checks that might cause linter errors
			debugInfo += fmt.Sprintf("", idTokenClaims.Sub)
			debugInfo += fmt.Sprintf("", idTokenClaims.Name)
			debugInfo += fmt.Sprintf("", idTokenClaims.Email)
			debugInfo += fmt.Sprintf("", idTokenClaims.Iss)
			debugInfo += fmt.Sprintf("", idTokenClaims.Aud)
			debugInfo += fmt.Sprintf("", idTokenClaims.Exp)
			debugInfo += fmt.Sprintf("", idTokenClaims.Iat)
			
			debugInfo += "
ClaimValue
sub%v
name%v
email%v
issuer%v
audience%v
expires at%v
issued at%v
" debugInfo += "
" } // Try to get access token info hasAccessToken := false var accessTokenErr error _, accessTokenErr = logtoClient.GetAccessToken("") if accessTokenErr == nil { hasAccessToken = true } debugInfo += "

Access Token Information

" if hasAccessToken { debugInfo += "
Access Token: Present (not displayed for security)
" } else { debugInfo += "
No resource-specific access token available
" } // Session information debugInfo += "

Session Information

" debugInfo += "
"
		
		// Get all keys from the session
		for _, key := range []string{"idToken", "accessToken", "refreshToken", "expiresAt"} {
			value := session.Get(key)
			debugInfo += "
" + key + ": " if value == nil { debugInfo += "Not set" } else if strValue, ok := value.(string); ok { if len(strValue) > 100 { debugInfo += strValue[:100] + "..." } else { debugInfo += strValue } } else { debugInfo += "Set (non-string value)" } debugInfo += "
" } debugInfo += "
" } else { debugInfo += "
Authentication Status: Not Logged In ✗
" debugInfo += "
Sign in to see detailed authentication information
" } // Config information (excluding secrets) debugInfo += "

Logto Configuration

" debugInfo += "
Endpoint: " + os.Getenv("LOGTO_ENDPOINT") + "
" debugInfo += "
App ID: " + os.Getenv("LOGTO_APP_ID") + "
" debugInfo += "
Redirect URI: " + os.Getenv("LOGTO_REDIRECT_URI") + "
" // Add links for authentication actions debugInfo += "

Authentication Actions

" debugInfo += `
Sign In
` debugInfo += `
Sign Out
` ctx.Data(http.StatusOK, "text/html; charset=utf-8", []byte(debugInfo)) } // SignInHandler starts the Logto sign-in flow func SignInHandler(ctx *gin.Context) { session := sessions.Default(ctx) logtoClient := client.NewLogtoClient(getLogtoConfig(), &SessionStorage{session: session}) redirectUri := os.Getenv("LOGTO_REDIRECT_URI") signInUri, err := logtoClient.SignIn(redirectUri) if err != nil { ctx.String(http.StatusInternalServerError, err.Error()) return } ctx.Redirect(http.StatusTemporaryRedirect, signInUri) } // CallbackHandler handles the Logto sign-in callback func CallbackHandler(ctx *gin.Context) { session := sessions.Default(ctx) logtoClient := client.NewLogtoClient(getLogtoConfig(), &SessionStorage{session: session}) err := logtoClient.HandleSignInCallback(ctx.Request) if err != nil { ctx.String(http.StatusInternalServerError, err.Error()) return } // Redirect to the frontend page instead of the backend auth page ctx.Redirect(http.StatusTemporaryRedirect, os.Getenv("BASE_URL")) } // SignOutHandler starts the Logto sign-out flow func SignOutHandler(ctx *gin.Context) { session := sessions.Default(ctx) logtoClient := client.NewLogtoClient(getLogtoConfig(), &SessionStorage{session: session}) postSignOutRedirectUri := os.Getenv("LOGTO_POST_SIGN_OUT_REDIRECT_URI") signOutUri, err := logtoClient.SignOut(postSignOutRedirectUri) if err != nil { ctx.String(http.StatusOK, err.Error()) return } ctx.Redirect(http.StatusTemporaryRedirect, signOutUri) } // UserIdTokenClaimsHandler returns the user's ID token claims as JSON func UserIdTokenClaimsHandler(ctx *gin.Context) { session := sessions.Default(ctx) logtoClient := client.NewLogtoClient(getLogtoConfig(), &SessionStorage{session: session}) idTokenClaims, err := logtoClient.GetIdTokenClaims() if err != nil { ctx.String(http.StatusOK, err.Error()) return } ctx.JSON(http.StatusOK, idTokenClaims) }