10 KiB
Backend Authentication Handlers - Code Reference
This file shows the exact backend code that was added to support the authentication dropdown menu.
File: internal/handlers/handlers.go
GetMe Handler (Lines 77-110)
// GetMe returns the current authenticated user
func GetMe(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
// Get user ID from JWT claims (set by middleware)
userIDInterface, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not found in context"})
return
}
userID, ok := userIDInterface.(string)
if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "invalid user ID format"})
return
}
// Fetch user from database
var user models.User
if err := db.First(&user, "id = ?", userID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
} else {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
}
return
}
// Return user info (excluding password)
c.JSON(http.StatusOK, gin.H{
"id": user.ID,
"email": user.Email,
"name": user.Username, // Return username as name for frontend
})
}
}
Logout Handler (Lines 112-119)
// Logout handles user logout
func Logout(c *gin.Context) {
// Clear the JWT cookie
c.SetCookie("auth_token", "", -1, "/", "", false, true)
c.JSON(http.StatusOK, gin.H{
"message": "successfully logged out",
})
}
File: internal/middleware/middleware.go
Updated AuthMiddleware (Lines 28-58)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
var tokenString string
// Try to get token from Authorization header first
authHeader := c.GetHeader("Authorization")
if authHeader != "" {
tokenString = strings.TrimPrefix(authHeader, "Bearer ")
} else {
// Try to get token from cookie
cookie, err := c.Cookie("auth_token")
if err != nil {
c.JSON(401, gin.H{"error": "Authorization required"})
c.Abort()
return
}
tokenString = cookie
}
if tokenString == "" {
c.JSON(401, gin.H{"error": "No token provided"})
c.Abort()
return
}
claims, err := auth.VerifyToken(tokenString)
if err != nil {
c.JSON(401, gin.H{"error": "Invalid token"})
c.Abort()
return
}
c.Set("user_id", claims.UserID)
c.Set("email", claims.Email)
c.Set("is_admin", claims.IsAdmin)
c.Next()
}
}
File: cmd/server/main.go
Updated Route Registration (Lines 117-122)
// Auth routes
auth := router.Group("/api/auth")
{
auth.POST("/signup", handlers.SignUp(db))
auth.POST("/signin", handlers.SignIn(db))
auth.GET("/me", middleware.AuthMiddleware(), handlers.GetMe(db)) // NEW
auth.POST("/logout", middleware.AuthMiddleware(), handlers.Logout) // NEW
}
Frontend Authentication JavaScript
This JavaScript was added to all 4 templates (index.html, browse.html, library.html, reader.html).
Authentication Check Function
async function checkAuth() {
try {
const response = await fetch('/api/auth/me', {
method: 'GET',
credentials: 'include' // Send cookies with request
});
if (response.ok) {
const user = await response.json();
showUserMenu(user); // User is authenticated
} else {
showLoginButtons(); // User not authenticated
}
} catch (error) {
console.error('Auth check failed:', error);
showLoginButtons(); // Default to login buttons on error
}
}
Show User Menu Function
function showUserMenu(user) {
authButtons.style.display = 'none';
userProfileBtn.style.display = 'flex';
// Set user initial (first letter of name or email)
const initial = (user.name || user.email)[0].toUpperCase();
document.getElementById('userInitial').textContent = initial;
// Set user info in dropdown header
document.getElementById('dropdownUserName').textContent = user.name || 'User';
document.getElementById('dropdownUserEmail').textContent = user.email;
}
Show Login Buttons Function
function showLoginButtons() {
authButtons.style.display = 'flex';
userProfileBtn.style.display = 'none';
}
Dropdown Toggle Logic
const userProfileBtn = document.getElementById('userProfileBtn');
const dropdownMenu = document.getElementById('dropdownMenu');
// Toggle dropdown on button click
userProfileBtn.addEventListener('click', (e) => {
e.stopPropagation();
dropdownMenu.classList.toggle('active');
});
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.auth-container')) {
dropdownMenu.classList.remove('active');
}
});
Logout Handler
const logoutBtn = document.getElementById('logoutBtn');
logoutBtn.addEventListener('click', async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include' // Send cookies with request
});
if (response.ok) {
showLoginButtons(); // Clear user menu
// Optional: redirect to home
// window.location.href = '/';
} else {
console.error('Logout failed:', response.statusText);
}
} catch (error) {
console.error('Logout error:', error);
}
});
Initialization on Page Load
// Run auth check when page loads
document.addEventListener('DOMContentLoaded', checkAuth);
Frontend Dropdown Menu CSS
Added to all 4 templates (approximately 95 lines).
Styles
/* Auth container for relative positioning */
.auth-container {
position: relative;
display: flex;
align-items: center;
gap: 1rem;
}
/* User profile button (circular avatar) */
.user-profile-btn {
display: none; /* Hidden by default, shown when logged in */
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background: var(--primary, #3b82f6);
color: white;
border: none;
cursor: pointer;
font-weight: bold;
font-size: 1.1rem;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.user-profile-btn:hover {
background: var(--primary-dark, #2563eb);
}
/* Dropdown menu container */
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
min-width: 200px;
margin-top: 0.5rem;
background: var(--card, #1a1a1a);
border-radius: 0.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: none;
flex-direction: column;
z-index: 1000;
}
.dropdown-menu.active {
display: flex;
}
/* User info section in dropdown header */
.dropdown-header {
padding: 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-size: 0.9rem;
}
.dropdown-user-name {
font-weight: 600;
color: white;
margin-bottom: 0.25rem;
}
.dropdown-user-email {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
}
/* Menu items container */
.dropdown-items {
display: flex;
flex-direction: column;
padding: 0.5rem;
}
/* Individual menu item */
.dropdown-item {
padding: 0.75rem 1rem;
color: white;
text-decoration: none;
cursor: pointer;
background: none;
border: none;
text-align: left;
transition: background 0.2s;
font-size: 0.95rem;
width: 100%;
}
.dropdown-item:hover {
background: rgba(255, 255, 255, 0.1);
border-radius: 0.25rem;
}
/* Divider between menu sections */
.dropdown-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 0.5rem 0;
}
/* Danger style for logout button */
.dropdown-item.danger {
color: #ef4444;
}
.dropdown-item.danger:hover {
background: rgba(239, 68, 68, 0.2);
}
How to Use This Code
1. GetMe Handler
Use this to allow frontend templates to check if a user is logged in and retrieve their information.
Request:
GET /api/auth/me
Cookie: auth_token=<jwt_token>
Success Response (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"name": "johndoe"
}
Error Response (401):
{"error": "Invalid token"}
2. Logout Handler
Use this to clear the user's session and log them out.
Request:
POST /api/auth/logout
Cookie: auth_token=<jwt_token>
Success Response (200):
{"message": "successfully logged out"}
3. AuthMiddleware
Use this middleware on any protected routes to automatically validate JWT tokens from cookies or headers.
protected := router.Group("/api")
protected.Use(middleware.AuthMiddleware()) // Validates JWT automatically
{
protected.GET("/library", handlers.GetUserLibrary(db))
protected.POST("/library/:id", handlers.AddToLibrary(db))
}
4. Frontend JavaScript
Add the JavaScript functions to your HTML templates and call checkAuth() on page load.
The functions handle:
- Checking if user is authenticated
- Displaying appropriate UI (login buttons or user menu)
- Toggling dropdown menu
- Handling logout
Integration Points
1. Database User Model
Must have these fields:
ID uuid.UUID- User IDEmail string- User email addressUsername string- Username (displayed as name in dropdown)Password string- Hashed password (not returned by API)
2. JWT Token
Must contain these claims:
user_id- User IDemail- User emailis_admin- Admin flag
3. HTTP-Only Cookie
JWT is stored in:
- Name:
auth_token - HttpOnly: true (security)
- Path:
/ - Expiration: 24 hours
Testing Commands
# Check if user is authenticated
curl -b "auth_token=<your_jwt_token>" http://localhost:8080/api/auth/me
# Logout user
curl -X POST -b "auth_token=<your_jwt_token>" http://localhost:8080/api/auth/logout
# Check without authentication (should fail)
curl http://localhost:8080/api/auth/me
Notes:
- All 4 templates have identical dropdown menu CSS and JavaScript
- The middleware supports both Authorization headers and cookies
- The frontend uses
credentials: 'include'to send cookies automatically - Logout endpoint clears the auth_token cookie by setting expiration to -1