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 += "| Claim | Value |
"
// Display the raw claims directly without any checks that might cause linter errors
debugInfo += fmt.Sprintf("| sub | %v |
", idTokenClaims.Sub)
debugInfo += fmt.Sprintf("| name | %v |
", idTokenClaims.Name)
debugInfo += fmt.Sprintf("| email | %v |
", idTokenClaims.Email)
debugInfo += fmt.Sprintf("| issuer | %v |
", idTokenClaims.Iss)
debugInfo += fmt.Sprintf("| audience | %v |
", idTokenClaims.Aud)
debugInfo += fmt.Sprintf("| expires at | %v |
", idTokenClaims.Exp)
debugInfo += fmt.Sprintf("| issued at | %v |
", idTokenClaims.Iat)
debugInfo += "
"
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 += ``
debugInfo += ``
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.JSON(http.StatusInternalServerError, gin.H{"error": 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.JSON(http.StatusInternalServerError, gin.H{"error": 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.JSON(http.StatusOK, gin.H{"error": 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.JSON(http.StatusOK, gin.H{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, idTokenClaims)
}