OAuth 2.0 и JWT авторизация на Go: практическое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в OAuth 2.0 и JWT на Go

Современные веб-приложения и API требуют надежной и безопасной системы аутентификации и авторизации. Два наиболее популярных стандарта в этой области — OAuth 2.0 и JWT (JSON Web Tokens). На языке Go (Golang) реализация этих механизмов становится особенно эффективной благодаря встроенной поддержке конкурентности, высокой производительности и богатой экосистеме библиотек.

В этой статье мы подробно разберем, как реализовать OAuth 2.0 и JWT авторизацию на Go. Вы узнаете, как настроить сервер, генерировать и проверять токены, а также защитить свои эндпоинты. Материал будет полезен как начинающим, так и опытным Go-разработчикам.

Основы OAuth 2.0 и JWT

Что такое OAuth 2.0?

OAuth 2.0 — это протокол авторизации, который позволяет приложениям получать ограниченный доступ к ресурсам пользователя без передачи пароля. Вместо этого используются токены доступа (access tokens). Основные участники процесса:

  • Resource Owner — пользователь, владеющий данными
  • Client — приложение, запрашивающее доступ
  • Authorization Server — сервер, выдающий токены
  • Resource Server — сервер, предоставляющий защищенные ресурсы

Что такое JWT?

JWT (JSON Web Token) — это компактный и самодостаточный формат для передачи данных между сторонами в виде JSON-объекта. Токен состоит из трех частей, разделенных точками:

header.payload.signature

Преимущества JWT:

  • Не требует хранения сессий на сервере (stateless)
  • Содержит всю необходимую информацию (claims)
  • Подписан для проверки целостности

Реализация JWT авторизации на Go

Для работы с JWT в Go мы будем использовать популярную библиотеку golang-jwt/jwt/v5. Установите ее командой:

go get github.com/golang-jwt/jwt/v5

Генерация JWT токена

Создадим функцию для генерации access и refresh токенов:

package auth

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("your-secret-key")

type Claims struct {
    UserID uint   `json:"user_id"`
    Email  string `json:"email"`
    jwt.RegisteredClaims
}

func GenerateAccessToken(userID uint, email string) (string, error) {
    claims := &Claims{
        UserID: userID,
        Email:  email,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "myapp",
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

func GenerateRefreshToken(userID uint) (string, error) {
    claims := &Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
        },
    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

Проверка JWT токена

Middleware для проверки токена в HTTP-запросах:

package middleware

import (
    "net/http"
    "strings"
    "github.com/gin-gonic/gin"
    "myapp/auth"
)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
            c.Abort()
            return
        }

        tokenString := strings.TrimPrefix(authHeader, "Bearer ")
        if tokenString == authHeader {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token format"})
            c.Abort()
            return
        }

        claims, err := auth.ValidateToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid or expired token"})
            c.Abort()
            return
        }

        c.Set("userID", claims.UserID)
        c.Set("email", claims.Email)
        c.Next()
    }
}

func ValidateToken(tokenString string) (*auth.Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &auth.Claims{}, func(token *jwt.Token) (interface{}, error) {
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, jwt.ErrSignatureInvalid
        }
        return jwtSecret, nil
    })

    if err != nil {
        return nil, err
    }

    claims, ok := token.Claims.(*auth.Claims)
    if !ok || !token.Valid {
        return nil, jwt.ErrTokenInvalid
    }

    return claims, nil
}

Интеграция с OAuth 2.0

Теперь реализуем полный поток OAuth 2.0 с использованием стороннего провайдера (например, Google). Для этого будем использовать библиотеку golang.org/x/oauth2.

Настройка OAuth 2.0 клиента

package oauth

import (
    "encoding/json"
    "net/http"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
)

var googleOauthConfig = &oauth2.Config{
    RedirectURL:  "http://localhost:8080/auth/google/callback",
    ClientID:     "your-client-id",
    ClientSecret: "your-client-secret",
    Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
    Endpoint:     google.Endpoint,
}

func HandleGoogleLogin(w http.ResponseWriter, r *http.Request) {
    url := googleOauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
    http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func HandleGoogleCallback(w http.ResponseWriter, r *http.Request) {
    code := r.URL.Query().Get("code")
    token, err := googleOauthConfig.Exchange(r.Context(), code)
    if err != nil {
        http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
        return
    }

    client := googleOauthConfig.Client(r.Context(), token)
    resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
    if err != nil {
        http.Error(w, "Failed to get user info", http.StatusInternalServerError)
        return
    }
    defer resp.Body.Close()

    var userInfo map[string]interface{}
    json.NewDecoder(resp.Body).Decode(&userInfo)

    // Генерируем наш JWT на основе полученных данных                

Блоги

Рекомендаций по книгам