Skip to content
Back to skills

Goth Echo Security

ASecurity

This skill should be used when the user asks to "integrate goth with echo", "oauth echo framework", "echo authentication", "goth session management", "oauth security", "secure oauth", "gorilla sessions", or needs help with session storage, security patterns, or Echo framework integration for Goth.

  • 666 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 8, 2026
databasesjavascriptgojavabashsqlgitdatabasefrontendsecuritydocumentation

Works with

  • cli

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill goth-echo-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Goth Echo Security?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Goth Echo Security
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-goth-echo-security/badge)](https://www.skillsdirectory.com/skills/majiayu000-goth-echo-security)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: goth-echo-security
description: This skill should be used when the user asks to "integrate goth with echo", "oauth echo framework", "echo authentication", "goth session management", "oauth security", "secure oauth", "gorilla sessions", or needs help with session storage, security patterns, or Echo framework integration for Goth.
---

# 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

```go
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:

```go
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:

```go
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:

```go
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:

```go
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:

```bash
# Generate 32-byte random secret
openssl rand -base64 32
```

### Storing User Data in Session

After successful authentication:

```go
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

```go
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:

```go
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:

```go
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:

```go
// 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

```go
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

```go
// Echo HTTPS redirect middleware
e.Pre(middleware.HTTPSRedirect())
```

### Token Storage Security

Never expose access tokens to the client:

```go
// 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:

```go
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:

```go
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:

```go
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

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.