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

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

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

Начать курс

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

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

Мы создадим простой REST API сервер, который будет выдавать JWT токены после успешной аутентификации через OAuth 2.0 провайдера (например, Google) и проверять их при каждом запросе.

1. Основы OAuth 2.0 и JWT

Прежде чем писать код, кратко разберемся с терминами.

  • OAuth 2.0 — протокол авторизации, который позволяет пользователям предоставлять третьим сторонам ограниченный доступ к своим ресурсам без передачи логина и пароля. Основные роли: Resource Owner (пользователь), Client (наше приложение), Authorization Server (сервер, выдающий токены), Resource Server (сервер с защищенными данными).
  • JWT — компактный URL-безопасный формат токена, который состоит из трех частей: header, payload и signature. JWT может содержать любые данные (claims) и подписывается секретным ключом или парой ключей RSA.

В нашей реализации OAuth 2.0 будет использоваться для получения identity токена от провайдера (Google), а затем мы сгенерируем собственный JWT для дальнейшей работы с нашим API.

2. Настройка проекта и зависимости

Создадим новый проект на Go и установим необходимые пакеты:

mkdir go-oauth-jwt
cd go-oauth-jwt
go mod init example.com/go-oauth-jwt
go get github.com/gin-gonic/gin
go get github.com/golang-jwt/jwt/v5
go get golang.org/x/oauth2
go get golang.org/x/oauth2/google

Мы будем использовать:

  • Gin — быстрый HTTP фреймворк для Go.
  • golang-jwt — библиотека для работы с JWT.
  • oauth2 — стандартная библиотека Go для OAuth 2.0.

Также вам потребуется зарегистрировать приложение в Google Cloud Console, создать OAuth 2.0 Client ID и Secret, и указать Redirect URI (например, http://localhost:8080/auth/google/callback).

3. Реализация OAuth 2.0 логина через Google

Начнем с создания конфигурации OAuth 2.0 и эндпоинтов для входа и обратного вызова.

package main

import (
    "crypto/rand"
    "encoding/base64"
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
    "golang.org/x/oauth2"
    "golang.org/x/oauth2/google"
)

var (
    googleOauthConfig *oauth2.Config
    oauthStateString  string
)

func init() {
    googleOauthConfig = &oauth2.Config{
        RedirectURL:  "http://localhost:8080/auth/google/callback",
        ClientID:     os.Getenv("GOOGLE_CLIENT_ID"),
        ClientSecret: os.Getenv("GOOGLE_CLIENT_SECRET"),
        Scopes:       []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
        Endpoint:     google.Endpoint,
    }
    // Генерируем случайный state токен для защиты от CSRF
    b := make([]byte, 32)
    rand.Read(b)
    oauthStateString = base64.StdEncoding.EncodeToString(b)
}

func main() {
    r := gin.Default()

    r.GET("/auth/google/login", handleGoogleLogin)
    r.GET("/auth/google/callback", handleGoogleCallback)

    r.Run(":8080")
}

func handleGoogleLogin(c *gin.Context) {
    url := googleOauthConfig.AuthCodeURL(oauthStateString)
    c.Redirect(http.StatusTemporaryRedirect, url)
}

func handleGoogleCallback(c *gin.Context) {
    state := c.Query("state")
    if state != oauthStateString {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid oauth state"})
        return
    }

    code := c.Query("code")
    token, err := googleOauthConfig.Exchange(c, code)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to exchange token"})
        return
    }

    // Здесь мы получаем информацию о пользователе
    client := googleOauthConfig.Client(c, token)
    resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo")
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get user info"})
        return
    }
    defer resp.Body.Close()

    // Декодируем ответ (упрощенно)
    // В реальном проекте используйте encoding/json
    c.JSON(http.StatusOK, gin.H{
        "message": "Login successful",
        "token":   token.AccessToken,
    })
}

Этот код реализует начальный этап OAuth 2.0: перенаправление пользователя на страницу входа Google и обработку обратного вызова с получением access token.

4. Генерация JWT токена после аутентификации

Теперь, когда у нас есть информация о пользователе, мы сгенерируем собственный JWT токен. Это позволит нашему API не зависеть от Google токена.

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

type Claims struct {
    Email string `json:"email"`
    Name  string `json:"name"`
    jwt.RegisteredClaims
}

var jwtKey = []byte("your-secret-key") // В продакшене используйте безопасное хранилище

func generateJWT(email, name string) (string, error) {
    expirationTime := time.Now().Add(24 * time.Hour)

    claims := &Claims{
        Email: email,
        Name:  name,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(expirationTime),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "go-oauth-jwt",
        },
    }

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

Обновим handleGoogleCallback, чтобы после получения данных пользователя генерировать JWT:

// В handleGoogleCallback после получения userinfo
// Предположим, мы получили email и name
email := "user@example.com" // Извлекаем из ответа Google
name := "John Doe"

jwtToken, err := generateJWT(email, name)
if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
    return
}

c.JSON(http.StatusOK, gin.H{
    "message":   "Login successful",
    "jwt_token": jwtToken,
})

Блоги

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