Authentication and authorization are fundamental aspects of securing any application, ensuring that only authorized users can access specific resources. In this tutorial, we'll explore how to implement robust authentication and authorization mechanisms using Go (Golang). We'll cover the basics of authentication, including user registration, login, and token-based sessions, as well as authorization strategies such as role-based access control (RBAC).
Before diving into the implementation, ensure you have the following:
net/http.First, create a new directory for your project and initialize it with Go modules:
mkdir auth-tutorial
cd auth-tutorial
go mod init auth-tutorial
We'll use the following dependencies:
github.com/gorilla/mux for routing.github.com/dgrijalva/jwt-go for JSON Web Tokens (JWT) handling.golang.org/x/crypto/bcrypt for password hashing.Install these packages using:
go get -u github.com/gorilla/mux
go get -u github.com/dgrijalva/jwt-go
go get -u golang.org/x/crypto/bcrypt
Create a models directory and define a user model:
// models/user.go
package models
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
}
Create a handlers directory and implement the registration handler:
// handlers/auth.go
package handlers
import (
"auth-tutorial/models"
"encoding/json"
"golang.org/x/crypto/bcrypt"
"net/http"
)
func Register(w http.ResponseWriter, r *http.Request) {
var user models.User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
// Hash the password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
http.Error(w, "Failed to hash password", http.StatusInternalServerError)
return
}
user.Password = string(hashedPassword)
// Save the user (in-memory for simplicity)
users[user.Username] = user
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
var users = make(map[string]models.User)
Add the login handler:
func Login(w http.ResponseWriter, r *http.Request) {
var credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
err := json.NewDecoder(r.Body).Decode(&credentials)
if err != nil {
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
user, exists := users[credentials.Username]
if !exists {
http.Error(w, "User not found", http.StatusUnauthorized)
return
}
// Compare the provided password with the stored hash
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(credentials.Password))
if err != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
// Generate a JWT token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": user.Username,
})
signedToken, err := token.SignedString([]byte("secret"))
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"token": signedToken})
}
Create a middleware to authenticate requests using JWT:
// middlewares/auth.go
package middlewares
import (
"auth-tutorial/models"
"encoding/json"
"github.com/dgrijalva/jwt-go"
"net/http"
)
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid
}
return []byte("secret"), nil
})
if err != nil || !token.Valid {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, "Invalid token claims", http.StatusInternalServerError)
return
}
username := claims["username"].(string)
user, exists := users[username]
if !exists {
http.Error(w, "User not found", http.StatusUnauthorized)
return
}
r = r.WithContext(context.WithValue(r.Context(), "user", &user))
next.ServeHTTP(w, r)
})
}
Define roles and implement RBAC:
// models/user.go
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
Role string `json:"role"`
}
const (
AdminRole = "admin"
UserRole = "user"
)
// middlewares/rbac.go
package middlewares
import (
"auth-tutorial/models"
"net/http"
)
func RBACMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(*models.User)
if user.Role != models.AdminRole {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
Create the main application file:
// main.go
package main
import (
"auth-tutorial/handlers"
"auth-tutorial/middlewares"
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
r := mux.NewRouter()
// Public routes
r.HandleFunc("/register", handlers.Register).Methods("POST")
r.HandleFunc("/login", handlers.Login).Methods("POST")
// Protected routes with authentication and RBAC
protectedRoutes := r.PathPrefix("/admin").Subrouter()
protectedRoutes.Use(middlewares.AuthMiddleware)
protectedRoutes.Use(middlewares.RBACMiddleware)
protectedRoutes.HandleFunc("/dashboard", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the admin dashboard!"))
}).Methods("GET")
log.Println("Server is running on :8080")
if err := http.ListenAndServe(":8080", r); err != nil {
log.Fatal(err)
}
}
You can test the application using tools like curl or Postman. Here are some example requests:
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password123"}'
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password123"}'
Use the token obtained from login to access protected routes:
curl -X GET http://localhost:8080/admin/dashboard \
-H "Authorization: <token>"
In this tutorial, we've covered the basics of authentication and authorization in Go using JWT. We implemented user registration, login, and protected routes with RBAC. These concepts are essential for securing your applications and ensuring that only authorized users can access sensitive resources.