goth-echo-security

Goth Echo Integration & Security

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "goth-echo-security" with this command: npx skills add linehaul-ai/linehaulai-claude-marketplace/linehaul-ai-linehaulai-claude-marketplace-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 &#x26;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 = &#x26;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: true on 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: true in production

  • Cookies use HttpOnly: true

  • Cookies use SameSite: Lax or Strict

  • 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 comparison

  • references/security-checklist.md

  • Security verification

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

slack-auth-security

No summary provided by upstream source.

Repository SourceNeeds Review
General

geospatial-postgis-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

rbac-authorization-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

quickbooks-online-api

No summary provided by upstream source.

Repository SourceNeeds Review