Skip to content

Go Authentication Implementation

Published: at 03:57 PM
package main

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/bcrypt"
	"time"
)

// User struct represents a user in the system
type User struct {
	ID       int
	Username string
	Password string
}

var users = make(map[string]User) // Simulate a simple in-memory database

// Secret key for signing JWTs (should be kept secret)
var jwtSecret = []byte("your_jwt_secret")

// JWT claims structure
type Claims struct {
	UserID int `json:"user_id"`
	jwt.StandardClaims
}

func signup(username, password string) error {
	// Check if the username already exists
	if _, exists := users[username]; exists {
		return fmt.Errorf("username already exists")
	}

	// Hash the password before saving it
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	// Create a new user
	userID := len(users) + 1
	newUser := User{
		ID:       userID,
		Username: username,
		Password: string(hashedPassword),
	}

	// Save the user in the database
	users[username] = newUser
	return nil
}

func login(username, password string) (string, string, error) {
	// Retrieve the user from the database
	user, exists := users[username]
	if !exists {
		return "", "", fmt.Errorf("user not found")
	}

	// Compare the hashed password with the provided password
	err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if err != nil {
		return "", "", fmt.Errorf("invalid password")
	}

	// Generate JWTs
	accessToken, err := generateAccessToken(user.ID)
	if err != nil {
		return "", "", err
	}

	refreshToken, err := generateRefreshToken(user.ID)
	if err != nil {
		return "", "", err
	}

	return accessToken, refreshToken, nil
}

func resetPassword(username, newPassword string) error {
	// Retrieve the user from the database
	user, exists := users[username]
	if !exists {
		return fmt.Errorf("user not found")
	}

	// Hash the new password before updating
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// Update the user's password
	user.Password = string(hashedPassword)
	users[username] = user

	return nil
}

func changePassword(userID int, currentPassword, newPassword string) error {
	// Retrieve the user from the database
	user, exists := getUserByID(userID)
	if !exists {
		return fmt.Errorf("user not found")
	}

	// Compare the current hashed password with the provided current password
	err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword))
	if err != nil {
		return fmt.Errorf("invalid current password")
	}

	// Hash the new password before updating
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// Update the user's password
	user.Password = string(hashedPassword)
	users[user.Username] = user

	return nil
}

func getUserByID(userID int) (User, bool) {
	for _, user := range users {
		if user.ID == userID {
			return user, true
		}
	}
	return User{}, false
}

func generateAccessToken(userID int) (string, error) {
	// Create the JWT claims
	claims := Claims{
		UserID: userID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 1).Unix(), // Access token expires in 1 hour
			IssuedAt:  time.Now().Unix(),
		},
	}

	// Create the access token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	accessToken, err := token.SignedString(jwtSecret)
	if err != nil {
		return "", err
	}

	return accessToken, nil
}

func generateRefreshToken(userID int) (string, error) {
	// Create the JWT claims
	claims := Claims{
		UserID: userID,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 24 * 7).Unix(), // Refresh token expires in 7 days
			IssuedAt:  time.Now().Unix(),
		},
	}

	// Create the refresh token
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	refreshToken, err := token.SignedString(jwtSecret)
	if err != nil {
		return "", err
	}

	return refreshToken, nil
}

func validateAccessToken(accessToken string) (int, error) {
	// Parse the token
	token, err := jwt.ParseWithClaims(accessToken, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})

	if err != nil {
		return 0, err
	}

	// Verify the token
	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return 0, fmt.Errorf("invalid access token")
	}

	return claims.UserID, nil
}

func refreshAccessToken(refreshToken string) (string, error) {
	// Parse the refresh token
	token, err := jwt.ParseWithClaims(refreshToken, &Claims{}, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})

	if err != nil {
		return "", err
	}

	// Verify the refresh token
	claims, ok := token.Claims.(*Claims)
	if !ok || !token.Valid {
		return "", fmt.Errorf("invalid refresh token")
	}

	// Generate a new access token
	newAccessToken, err := generateAccessToken(claims.UserID)
	if err != nil {
		return "", err
	}

	return newAccessToken, nil
}

func main() {
	// Sign up a new user
	err := signup("john_doe", "password123")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Login and get tokens
	accessToken, refreshToken, err := login("john_doe", "password123")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Simulate token validation (for demonstration purposes)
	userID, err := validateAccessToken(accessToken)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("User ID: %d\n", userID)

	// Refresh access token
	newAccessToken, err := refreshAccessToken(refreshToken)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Simulate token validation for the new access token (for demonstration purposes)
	userIDWithNewToken, err := validateAccessToken(newAccessToken)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("User ID with new access token: %d\n", userIDWithNewToken)

	// Reset password
	err = resetPassword("john_doe", "newpassword456")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Change password
	err = changePassword(userID, "newpassword456", "updatedpassword789")
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Validate access token again after changing password
	userIDAfterChange, err := validateAccessToken(accessToken)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("User ID after password change: %d\n", userIDAfterChange)
}

This Go code provides a simplified implementation of user authentication and authorization using JSON Web Tokens (JWT) in Golang. Let’s break down the code step by step:

User Struct and Database Setup:

type User struct {
	ID       int
	Username string
	Password string
}

Defines a User struct to represent a user in the system with attributes such as ID, username, and password.

var users = make(map[string]User)

Creates an in-memory map (users) to simulate a simple database where user data is stored.

var jwtSecret = []byte("your_jwt_secret")

Defines a secret key used for signing and verifying JWTs. In a real-world scenario, this should be kept secure and not hardcoded in the code. JWT Claims Structure:

type Claims struct {
	UserID int `json:"user_id"`
	jwt.StandardClaims
}

Defines a Claims struct to represent the claims included in JWTs. In this case, it includes a UserID field and inherits from jwt.StandardClaims for standard JWT claims. Signup Function:

func signup(username, password string) error {
    // ...
}

Implements user signup by checking if the username already exists, hashing the password, creating a new user, and saving it in the simulated database. Login Function:

func login(username, password string) (string, string, error) {
    // ...
}

Handles user login by retrieving the user from the database, comparing the hashed password, and generating JWTs (access token and refresh token) if login is successful. Reset Password Function:

func resetPassword(username, newPassword string) error {
    // ...
}

Allows users to reset their password by retrieving the user from the database and updating the hashed password. Change Password Function:

func changePassword(userID int, currentPassword, newPassword string) error {
    // ...
}

Enables users to change their password by validating the current password, hashing the new password, and updating the user’s password in the database. GetUserByID Function:

func getUserByID(userID int) (User, bool) {
    // ...
}

Retrieves a user from the database based on their ID. Generate Access Token Function:

func generateAccessToken(userID int) (string, error) {
    // ...
}

Creates a new access token with user-specific claims and an expiration time. Generate Refresh Token Function:

func generateRefreshToken(userID int) (string, error) {
    // ...
}

Generates a refresh token with user-specific claims and a longer expiration time. Validate Access Token Function:

func validateAccessToken(accessToken string) (int, error) {
    // ...
}

Parses and validates an access token, returning the user ID if successful. Refresh Access Token Function:

func refreshAccessToken(refreshToken string) (string, error) {
    // ...
}

Parses and validates a refresh token, then generates a new access token if successful. Main Function:

func main() {
    // ...
}

Demonstrates the usage of the implemented functions, including user signup, login, password reset, password change, and access token refresh. This code provides a foundation for implementing user authentication and authorization using JWTs in a Go application. It’s important to note that in a production environment, additional security measures, error handling, and database integration would be necessary. The secret key should be securely managed, and user passwords should be handled with care, potentially using more advanced hashing techniques.