goth-echo-security
Goth Echo Integration & Security
Expert guidance for integrating github.com/markbates/goth with the Echo web framework and implementing secure session management.
Echo Framework Integration
Basic Route Setup
import (
"github.com/labstack/echo/v4"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/google"
)
func main() {
e := echo.New()
// Auth routes
e.GET("/auth/:provider", handleAuth)
e.GET("/auth/:provider/callback", handleCallback)
e.GET("/logout", handleLogout)
e.Start(":3000")
}
Provider Name from Echo Context
Override Gothic's provider getter to use Echo's path parameters:
func init() {
gothic.GetProviderName = func(r *http.Request) (string, error) {
// Extract from Echo's :provider path param
// The request context contains Echo's params
provider := r.URL.Query().Get(":provider")
if provider == "" {
// Fallback: parse from path
parts := strings.Split(r.URL.Path, "/")
for i, p := range parts {
if p == "auth" && i+1 < len(parts) {
return parts[i+1], nil
}
}
}
if provider == "" {
return "", errors.New("no provider specified")
}
return provider, nil
}
}
Echo Handler Wrappers
Wrap Gothic handlers for Echo compatibility:
func handleAuth(c echo.Context) error {
// Set provider in query for Gothic
q := c.Request().URL.Query()
q.Set(":provider", c.Param("provider"))
c.Request().URL.RawQuery = q.Encode()
gothic.BeginAuthHandler(c.Response(), c.Request())
return nil
}
func handleCallback(c echo.Context) error {
q := c.Request().URL.Query()
q.Set(":provider", c.Param("provider"))
c.Request().URL.RawQuery = q.Encode()
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
// Store user in session, redirect to dashboard
return c.JSON(http.StatusOK, map[string]interface{}{
"name": user.Name,
"email": user.Email,
})
}
func handleLogout(c echo.Context) error {
gothic.Logout(c.Response(), c.Request())
return c.Redirect(http.StatusTemporaryRedirect, "/")
}
Echo Middleware for Auth
Create middleware to protect routes:
func RequireAuth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
session, err := gothic.Store.Get(c.Request(), gothic.SessionName)
if err != nil || session.Values["user_id"] == nil {
return c.Redirect(http.StatusTemporaryRedirect, "/login")
}
return next(c)
}
}
// Usage
e.GET("/dashboard", handleDashboard, RequireAuth)
Session Management
Default Cookie Store
Gothic uses gorilla/sessions CookieStore by default:
import "github.com/gorilla/sessions"
func initSessionStore() {
key := []byte(os.Getenv("SESSION_SECRET"))
if len(key) < 32 {
log.Fatal("SESSION_SECRET must be at least 32 bytes")
}
store := sessions.NewCookieStore(key)
store.MaxAge(86400 * 30) // 30 days
store.Options.Path = "/"
store.Options.HttpOnly = true
store.Options.Secure = os.Getenv("ENV") == "production"
store.Options.SameSite = http.SameSiteLaxMode
gothic.Store = store
}
Session Secret Generation
Generate a secure session secret:
# Generate 32-byte random secret
openssl rand -base64 32
Storing User Data in Session
After successful authentication:
func handleCallback(c echo.Context) error {
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return err
}
// Get or create session
session, _ := gothic.Store.Get(c.Request(), "user-session")
// Store user data
session.Values["user_id"] = user.UserID
session.Values["email"] = user.Email
session.Values["name"] = user.Name
session.Values["access_token"] = user.AccessToken
session.Values["provider"] = user.Provider
// Save session
if err := session.Save(c.Request(), c.Response()); err != nil {
return err
}
return c.Redirect(http.StatusTemporaryRedirect, "/dashboard")
}
Retrieving User from Session
func getCurrentUser(c echo.Context) (*UserInfo, error) {
session, err := gothic.Store.Get(c.Request(), "user-session")
if err != nil {
return nil, err
}
userID, ok := session.Values["user_id"].(string)
if !ok || userID == "" {
return nil, errors.New("not authenticated")
}
return &UserInfo{
UserID: userID,
Email: session.Values["email"].(string),
Name: session.Values["name"].(string),
Provider: session.Values["provider"].(string),
}, nil
}
Alternative Session Stores
Redis Session Store
For distributed deployments:
import "github.com/rbcervilla/redisstore/v9"
func initRedisStore() {
client := redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_URL"),
})
store, err := redisstore.NewRedisStore(context.Background(), client)
if err != nil {
log.Fatal(err)
}
store.KeyPrefix("session_")
store.Options(sessions.Options{
Path: "/",
MaxAge: 86400 * 30,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
gothic.Store = store
}
Database Session Store
For PostgreSQL with pgx:
import "github.com/antonlindstrom/pgstore"
func initPgStore() {
store, err := pgstore.NewPGStoreFromPool(
dbPool,
[]byte(os.Getenv("SESSION_SECRET")),
)
if err != nil {
log.Fatal(err)
}
store.Options = &sessions.Options{
Path: "/",
MaxAge: 86400 * 30,
HttpOnly: true,
Secure: true,
}
gothic.Store = store
}
See references/session-storage-options.md for detailed comparison.
Security Best Practices
CSRF Protection with State Parameter
Goth automatically handles the OAuth state parameter for CSRF protection. Verify it's working:
// Gothic handles state internally, but verify in callback
func handleCallback(c echo.Context) error {
// State is validated by gothic.CompleteUserAuth
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
// State mismatch will cause error here
log.Printf("Auth failed (possible CSRF): %v", err)
return c.Redirect(http.StatusTemporaryRedirect, "/login?error=invalid_state")
}
// ...
}
Secure Cookie Configuration
store.Options = &sessions.Options{
Path: "/",
Domain: "", // Current domain only
MaxAge: 86400 * 7, // 7 days
Secure: true, // HTTPS only
HttpOnly: true, // No JavaScript access
SameSite: http.SameSiteLaxMode, // CSRF protection
}
HTTPS Requirements
In production, always use HTTPS:
- Set
Secure: trueon cookies - Use HTTPS callback URLs in provider configuration
- Redirect HTTP to HTTPS
// Echo HTTPS redirect middleware
e.Pre(middleware.HTTPSRedirect())
Token Storage Security
Never expose access tokens to the client:
// DON'T: Send token to frontend
return c.JSON(200, map[string]string{
"access_token": user.AccessToken, // Dangerous!
})
// DO: Store token server-side only
session.Values["access_token"] = user.AccessToken
Session Hijacking Prevention
Regenerate session ID after authentication:
func handleCallback(c echo.Context) error {
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return err
}
// Get existing session
oldSession, _ := gothic.Store.Get(c.Request(), "user-session")
// Copy values to new session (forces new ID)
oldSession.Options.MaxAge = -1 // Delete old session
oldSession.Save(c.Request(), c.Response())
newSession, _ := gothic.Store.New(c.Request(), "user-session")
newSession.Values["user_id"] = user.UserID
newSession.Values["email"] = user.Email
newSession.Save(c.Request(), c.Response())
return c.Redirect(http.StatusTemporaryRedirect, "/dashboard")
}
Rate Limiting Auth Endpoints
Protect against brute force:
import "github.com/labstack/echo/v4/middleware"
// Limit auth endpoints
authGroup := e.Group("/auth")
authGroup.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(
rate.Limit(10), // 10 requests per second
)))
Token Refresh Pattern
Keep access tokens fresh:
func refreshTokenIfNeeded(c echo.Context) error {
session, _ := gothic.Store.Get(c.Request(), "user-session")
expiresAt, ok := session.Values["expires_at"].(time.Time)
if !ok || time.Until(expiresAt) > 5*time.Minute {
return nil // Token still valid
}
providerName := session.Values["provider"].(string)
provider, _ := goth.GetProvider(providerName)
if !provider.RefreshTokenAvailable() {
return nil
}
refreshToken := session.Values["refresh_token"].(string)
token, err := provider.RefreshToken(refreshToken)
if err != nil {
// Refresh failed - force re-login
return c.Redirect(http.StatusTemporaryRedirect, "/logout")
}
session.Values["access_token"] = token.AccessToken
session.Values["expires_at"] = token.Expiry
if token.RefreshToken != "" {
session.Values["refresh_token"] = token.RefreshToken
}
session.Save(c.Request(), c.Response())
return nil
}
Security Checklist
Before deploying:
- SESSION_SECRET is at least 32 random bytes
- Cookies use
Secure: truein production - Cookies use
HttpOnly: true - Cookies use
SameSite: LaxorStrict - HTTPS is enforced in production
- Callback URLs use HTTPS
- Access tokens stored server-side only
- Rate limiting on auth endpoints
- Session regeneration after login
- Error messages don't leak sensitive info
See references/security-checklist.md for complete checklist.
Quick Reference
| Task | Code |
|---|---|
| Set session store | gothic.Store = store |
| Get session | gothic.Store.Get(r, "name") |
| Save session | session.Save(r, w) |
| Delete session | session.Options.MaxAge = -1 |
| Secure cookie | Secure: true, HttpOnly: true |
Related Skills
- goth-fundamentals - Core Goth concepts
- goth-providers - Provider configuration
Reference Documentation
references/session-storage-options.md- Storage comparisonreferences/security-checklist.md- Security verification
More from linehaul-ai/linehaulai-claude-marketplace
geospatial-postgis-patterns
Implement geofences, spatial queries, real-time tracking, and mapping features in laneweaverTMS using PostGIS and PGRouting. Use when building location-based features, distance calculations, ETA predictions, or fleet visualization.
83quickbooks-online-api
Expert guide for QuickBooks Online API integration covering authentication, CRUD operations, batch processing, and best practices for invoicing, payments, and customer management.
61rbac-authorization-patterns
Provide patterns for implementing Role-Based Access Control and multi-tenant authorization in laneweaverTMS. Use when implementing user roles, permissions, tenant isolation, Echo authorization middleware, RLS policies for multi-tenant access, or JWT claims structure for freight brokerage applications.
61slack-block-kit
Build Slack Block Kit UIs for messages, modals, and Home tabs. Use when creating Slack notifications, interactive forms, bot responses, app dashboards, or any Slack UI. Covers blocks (Section, Actions, Input, Header), elements (Buttons, Selects, Date pickers), composition objects, and the slack-block-builder library.
44svelte-flow
Build node-based editors, interactive diagrams, and flow visualizations using Svelte Flow. Use when creating workflow editors, data flow diagrams, organizational charts, mindmaps, process visualizations, DAG editors, or any interactive node-graph UI. Supports custom nodes/edges, layouts (dagre, hierarchical), animations, and advanced features like proximity connect, floating edges, and contextual zoom.
35testcontainers-go
Use this skill when writing Go integration tests with Docker containers, using testcontainers-go modules (postgres, redis, kafka, etc.), setting up container-based test infrastructure, or configuring container networking and wait strategies. Covers 62+ pre-configured modules, cleanup patterns, and multi-container setups.
34