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

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

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

Начать курс

Введение: зачем нужны OAuth 2.0 и JWT в Go?

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

В этой статье мы разберём, как реализовать безопасную авторизацию на Go с нуля. Вы узнаете:

  • Чем отличается OAuth 2.0 от JWT и как они работают вместе.
  • Как генерировать и проверять JWT-токены на Go.
  • Как построить минимальную OAuth 2.0-авторизацию (Client Credentials flow).
  • Как защитить REST API с помощью middleware.

Статья рассчитана на начинающих разработчиков, которые уже знакомы с основами Go и HTTP-серверами.

Основы: OAuth 2.0 и JWT — не одно и то же

Многие новички путают эти понятия. Давайте сразу расставим точки над i.

Что такое OAuth 2.0?

OAuth 2.0 — это протокол авторизации, который позволяет одному сервису (клиенту) получить ограниченный доступ к ресурсам другого сервиса (сервера) от имени пользователя. Представьте, что вы хотите войти на сайт через Google. Сайт не получает ваш пароль — он получает от Google временный токен, который даёт доступ только к вашей почте (и то с вашего согласия).

Основные роли в OAuth 2.0:

  • Resource Owner — владелец данных (обычно пользователь).
  • Client — приложение, которое хочет получить доступ.
  • Authorization Server — сервер, который выдаёт токены после проверки.
  • Resource Server — сервер, который хранит защищённые данные и проверяет токены.

Что такое JWT?

JWT (JSON Web Token) — это формат самого токена. Он представляет собой строку из трёх частей, разделённых точками: header.payload.signature. JWT может содержать любые данные (например, ID пользователя, роль, срок действия) и подписан секретным ключом, чтобы его нельзя было подделать.

OAuth 2.0 не привязан к JWT — токен может быть любой строкой. Однако на практике JWT используется чаще всего, потому что он самодостаточен: серверу не нужно хранить сессии в базе данных, всю информацию можно проверить на лету.

Реализация JWT на Go: генерация и проверка токенов

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

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

Генерация Access Token

Создадим функцию, которая принимает ID пользователя и возвращает подписанный JWT-токен с временем жизни 1 час.

package main

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

var jwtSecret = []byte("your-secret-key") // В продакшене храните в .env!

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

func GenerateAccessToken(userID uint) (string, error) {
    claims := Claims{
        UserID: userID,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "my-app",
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    return token.SignedString(jwtSecret)
}

Проверка и парсинг JWT

Теперь напишем middleware, который будет проверять токен из заголовка Authorization: Bearer <token>.

func ValidateToken(tokenString string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        // Проверяем, что используется правильный метод подписи
        if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
        }
        return jwtSecret, nil
    })
    if err != nil {
        return nil, err
    }
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    return nil, fmt.Errorf("invalid token")
}

Middleware для защиты эндпоинтов

Оборачиваем любой эндпоинт в middleware, который извлекает пользователя из токена:

func AuthMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        authHeader := r.Header.Get("Authorization")
        if authHeader == "" {
            http.Error(w, "Missing token", http.StatusUnauthorized)
            return
        }
        // Ожидаем формат "Bearer <token>"
        parts := strings.Split(authHeader, " ")
        if len(parts) != 2 || parts[0] != "Bearer" {
            http.Error(w, "Invalid token format", http.StatusUnauthorized)
            return
        }
        claims, err := ValidateToken(parts[1])
        if err != nil {
            http.Error(w, "Invalid or expired token", http.StatusUnauthorized)
            return
        }
        // Сохраняем данные пользователя в контекст запроса
        ctx := context.WithValue(r.Context(), "userID", claims.UserID)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Реализация OAuth 2.0 на Go: Client Credentials Flow

Для простоты разберём самый распространённый сценарий для серверных приложений — Client Credentials Flow. В нём нет пользователя; два сервера обмениваются токенами напрямую.

1. Эндпоинт для выдачи токена

Создаём обработчик /oauth/token, который проверяет client_id и client_secret и возвращает JWT.

func TokenHandler(w http.ResponseWriter, r *http.Request) {
    // В реальном проекте client_id/secret проверяются из БД
    clientID := r.PostFormValue("client_id")
    clientSecret := r.PostFormValue("client_secret")
    if clientID != "my-client" || clientSecret != "my-secret" {
        http.Error(w, `{"error":"invalid_client"}`, http.StatusUnauthorized                

Блоги

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