Зарегистрируйтесь, чтобы продолжить обучение

JWT-авторизация на сервере Веб-разработка на Go

Представим, что мы разрабатываем страницу с профилем пользователя в социальной сети. У пользователей есть возможность редактировать свои профили. Нам нужно определять, что HTTP-запрос на изменение профиля пришел от владельца профиля. Если не сделать такую проверку, то злоумышленники смогут изменять данные профилей других пользователей.

В этом уроке мы разберем тему JWT-авторизации и ее реализацию в Go. Это важная тема, потому что авторизация — это ключевая часть безопасности любого веб-приложения.

Аутентификация и авторизация

Представим, что мы хотим войти в свой аккаунт социальной сети. Веб-сайту нужно понять, что мы владеем этим аккаунтом. Для этого нам нужно предоставить корректные учетные данные. Обычно это логин и пароль. Веб-сайт проверяет эти данные и, если они верны, мы входим в свой аккаунт. Этот процесс называется аутентификацией:

set

Допустим, после успешной аутентификации мы заходим на страницу со своим профилем. Так как мы уже были аутентифицированы, мы можем отправить запрос на редактирование данных профиля. Веб-сайт проверяет, что запрос пришел от владельца профиля, и позволяет изменять данные. Процесс проверки прав на осуществление действий называется авторизацией:

set

Таким образом, аутентификация — это процесс проверки учетных данных, а авторизация — это процесс проверки прав на осуществление действий. В примере социальной сети аутентификация происходит при логине, а авторизация при каждом запросе после аутентификации.

Процесс авторизации происходит часто, и если допустить ошибку в системе авторизации, то злоумышленники смогут осуществлять действия от имени других пользователей. Например, сделать покупку от их имени или узнать конфиденциальную информацию. Поэтому важно знать безопасные и проверенные способы авторизации.

Два самых популярных способа авторизации:

  • С помощью сессий
  • С помощью токенов

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

JWT-авторизация

JWT (JSON Web Token) — это специальный формат токена, который позволяет безопасно передавать данные между клиентом и сервером. Например, клиентом может быть веб-браузер или мобильное приложение, сервером — сервер с Go веб-приложением.

JWT-токен состоит из трех частей, которые разделены точкой:

  • Header или заголовок — информация о токене, тип токена и алгоритм шифрования
  • Payload или полезные данные — данные, которые мы хотим передать в токене. Например, имя пользователя, его роль, истекает ли токен. Эти данные представлены в виде JSON-объекта
  • Signature или подпись — подпись токена, которая позволяет проверить, что токен не был изменен

Обычный токен имеет формат:

xxxxx.yyyyy.zzzzz

Рассмотрим пример реального токена с разбором каждой части. Предположим, наше веб-приложение сгенерировало следующий JWT-токен:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Заголовок обычно состоит из JSON-объекта с двумя свойствами:

  • Тип токена, который в нашем случае — JWT
  • Алгоритм шифрования, который в нашем случае — HMAC SHA256

Далее этот JSON-объект хэшируется с помощью Base64Url-кодирования, чтобы представить его в виде компактной строки.

Таким образом, в нашем примере заголовок JWT-токена имеет следующее значение:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

Вторая часть токена — это полезная нагрузка в виде JSON-объекта. Она содержит различные данные об авторизованном пользователе. Значение этой части JWT-токена различно в каждом веб-приложении. Мы можем записать здесь любые публичные данные, которые могут быть полезны при авторизации.

Как и заголовок JWT-токена, полезная нагрузка хэшируется с помощью Base64Url-кодирования для представления в виде компактной строки.

В нашем примере полезная нагрузка JWT-токена имеет следующее значение:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Названия некоторых полей могут показаться непонятными с первого взгляда. Например, поле sub означает идентификатор пользователя, а поле iat — время создания токена. При составлении полей полезной нагрузки рекомендуется учитывать имена из документации IANA (Internet Assigned Numbers Authority). Это поможет избежать конфликтов имен с общепринятыми нормами. Поэтому мы использовали название sub, вместо привычного user_id.

Основная причина, почему названия полей в полезной нагрузке JWT-токена пишутся сокращенно, — это уменьшение размера токена после шифрования.

Signature

Чтобы создать подпись, мы должны взять закодированный заголовок, закодированную полезную нагрузку, секретную строку и зашифровать эти данные. При этом нужно использовать алгоритм шифрования из заголовка JWT-токена.

В нашем примере для создания подписи используется алгоритм шифрования HMAC SHA256 и секретная строка "your-256-bit-secret":

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

Подпись используются, чтобы проверить, что сообщение не было изменено при передаче. Она также позволяет подтвердить, что отправитель JWT-токена является тем, кем он представляется.

Собираем все части JWT-токена

В результате генерации JWT-токена получаются три Base64-URL-закодированные строки, которые разделены точкой. Значение JWT-токена является компактным и легко передается в HTTP-запросах.

Если вы хотите попрактиковаться с JWT, можно использовать онлайн инструмент jwt.io Debugger для декодирования, проверки и генерации JWT-токенов.

Мы разобрали, из чего состоит JWT-токен. Теперь рассмотрим алгоритм работы с JWT-токеном в веб-приложениях.

Алгоритм работы с JWT-токеном

Процесс аутентификации и авторизации с JWT-токеном между веб-браузером и веб-приложением выглядит следующим образом:

  1. Веб-браузер отправляет запрос веб-приложению с логином и паролем
  2. Веб-приложение проверяет логин и пароль, и если они верны, то генерирует JWT-токен и отправляет его веб-браузеру. При генерации JWT-токена веб-приложение ставит подпись секретным ключом, который хранится только в веб-приложении
  3. Веб-браузер сохраняет JWT-токен и отправляет его вместе с каждым запросом в веб-приложение
  4. Веб-приложение проверяет JWT-токен и если он верный, то выполняет действие от имени авторизованного пользователя

Безопасность коммуникации между веб-браузером и веб-приложением заключается в том, что токены генерируются и подписываются только со стороны веб-приложения. Злоумышленник не сможет подделать токен, так как не знает секретный ключ, который используется для подписи токена.

Подпись токена происходит с помощью шифрования. С помощью подписи веб-приложение проверяет, что токен действительно был сгенерирован им. Шифрование может осуществляться различными алгоритмами. Например, алгоритмом HS256 — HMAC с SHA-256.

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

JWT-авторизация в Go веб-приложении

Реализуем аутентификацию и авторизацию в социальной сети, которая написана на Go с использованием микрофреймворка Fiber. Для этого реализуем следующие функции:

  • Регистрация пользователя
  • Вход в аккаунт — аутентификация
  • Получение информации о своем аккаунте — только для авторизованных пользователей

Чтобы упростить работу, мы не будем описывать многие важные части в системах аутентификации пользователей. Например, мы не будем проверять HTTP-запросы, использовать базы данных или хэшировать хранимые пароли для безопасности. Вы можете улучшить эти части веб-приложения самостоятельно.

Регистрация аккаунта

Начнем разработку социальной сети с функции регистрации аккаунта. Когда пользователь заходит на веб-сайт, он видит форму регистрации с тремя полями: имя, электронная почта и пароль. После заполнения формы пользователь нажимает кнопку «Зарегистрироваться», и веб-браузер отправляет HTTP-запрос POST /register в наше веб-приложение. Мы реализуем обработчик этого HTTP-запроса следующим образом:

package main

import (
    "errors"
    "fmt"
    "github.com/gofiber/fiber/v2"
    "github.com/sirupsen/logrus"
)

func main() {
    app := fiber.New()

    authHandler := &AuthHandler{&AuthStorage{map[string]User{}}}

    app.Post("/register", authHandler.Register)

    logrus.Fatal(app.Listen(":80"))
}

type (
    // Обработчик HTTP-запросов на регистрацию и аутентификацию пользователей
    AuthHandler struct {
        storage *AuthStorage
    }

    // Хранилище зарегистрированных пользователей
    // Данные хранятся в оперативной памяти
    AuthStorage struct {
        users map[string]User
    }

    // Структура данных с информацией о пользователе
    User struct {
        Email    string
        Name     string
        password string
    }
)

//  Структура HTTP-запроса на регистрацию пользователя
type RegisterRequest struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

// Обработчик HTTP-запросов на регистрацию пользователя
func (h *AuthHandler) Register(c *fiber.Ctx) error {
    regReq := RegisterRequest{}
    if err := c.BodyParser(&regReq); err != nil {
        return fmt.Errorf("body parser: %w", err)
    }

    // Проверяем, что пользователь с таким email еще не зарегистрирован
    if _, exists := h.storage.users[regReq.Email]; exists {
        return errors.New("the user already exists")
    }

    // Сохраняем в память нового зарегистрированного пользователя
    h.storage.users[regReq.Email] = User{
        Email:    regReq.Email,
        Name:     regReq.Name,
        password: regReq.Password,
    }

    return c.SendStatus(fiber.StatusCreated)
}

Когда приходит HTTP-запрос на регистрацию нового пользователя, веб-приложение проверяет, что в хранилище еще не существует пользователя с такой электронной почтой. Если пользователь с такой электронной почтой уже есть, то веб-приложение возвращает ошибку. В обратном случае все данные пользователя сохраняются в оперативной памяти веб-приложения.

После регистрации пользователь может войти в свой аккаунт, и далее мы реализуем эту возможность.

Вход в аккаунт

Когда пользователь заходит на страницу входа в аккаунт, он видит форму с двумя полями: электронная почта и пароль. Эти поля являются учетными данными пользователя. После заполнения формы пользователь нажимает кнопку «Войти», и веб-браузер отправляет HTTP-запрос POST /login в наше веб-приложение. Обработчик этого запроса будет выглядеть следующим образом:

// Структура HTTP-запроса на вход в аккаунт
type LoginRequest struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}

// Структура HTTP-ответа на вход в аккаунт
// В ответе содержится JWT-токен авторизованного пользователя
type LoginResponse struct {
    AccessToken string `json:"access_token"`
}

var (
    errBadCredentials = errors.New("email or password is incorrect")
)

// Секретный ключ для подписи JWT-токена
// Необходимо хранить в безопасном месте
var jwtSecretKey = []byte("very-secret-key")

// Обработчик HTTP-запросов на вход в аккаунт
func (h *AuthHandler) Login(c *fiber.Ctx) error {
    regReq := LoginRequest{}
    if err := c.BodyParser(&regReq); err != nil {
        return fmt.Errorf("body parser: %w", err)
    }

    // Ищем пользователя в памяти приложения по электронной почте
    user, exists := h.storage.users[regReq.Email]
    // Если пользователь не найден, возвращаем ошибку
    if !exists {
        return errBadCredentials
    }
    // Если пользователь найден, но у него другой пароль, возвращаем ошибку
    if user.password != regReq.Password {
        return errBadCredentials
    }

    // Генерируем JWT-токен для пользователя,
    // который он будет использовать в будущих HTTP-запросах

    // Генерируем полезные данные, которые будут храниться в токене
    payload := jwt.MapClaims{
        "sub":  user.Email,
        "exp":  time.Now().Add(time.Hour * 72).Unix(),
    }

    // Создаем новый JWT-токен и подписываем его по алгоритму HS256
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)

    t, err := token.SignedString(jwtSecretKey)
    if err != nil {
        logrus.WithError(err).Error("JWT token signing")
        return c.SendStatus(fiber.StatusInternalServerError)
    }

    return c.JSON(LoginResponse{AccessToken: t})
}

Когда приходит HTTP-запрос на вход в аккаунт, веб-приложение проверяет, что в хранилище существует пользователь с такой электронной почтой и паролем. Если учетные данные некорректны, то веб-приложение возвращает ошибку. В обратном случае пользователь успешно прошел аутентификацию, и веб-приложение генерирует и возвращает JWT-токен. Его пользователь будет использовать в будущих HTTP-запросах, чтобы авторизоваться.

Чтобы сгенерировать JWT-токен, мы используем библиотеку jwt-go. Благодаря этому все тонкости формирования и шифрования токена скрыты от нас. Все, что от нас требуется, — это указать секретный ключ для подписи токена и полезные данные, которые будут храниться в токене.

В полезных данных JWT-токена мы записываем электронную почту пользователя как идентификатор. Также записываем время, когда этот токен будет недействителен. В нашем примере время жизни JWT-токена составляет 72 часа. Когда это время истечет, пользователь должен будет войти в аккаунт заново.

Теперь в нашей социальной сети пользователи могут регистрироваться и аутентифицироваться — входить в свои аккаунты. Далее реализуем функцию получения информации о своем аккаунте, которая будет доступна только авторизованным пользователям.

Получение информации о своем аккаунте для авторизованных пользователей

Когда пользователь прошел аутентификацию, он получил JWT-токен, который будет использовать в последующих HTTP-запросах для авторизации. Есть разные способы передавать токен в HTTP-запросе. Для этого можно использовать заголовок Authorization, параметр запроса или даже куки веб-браузера. Мы будем использовать заголовок Authorization, так как это наиболее распространенный способ передачи JWT-токена в HTTP-запросе.

Когда пользователь заходит на свою страницу, веб-браузер отправляет HTTP-запрос GET /profile в наше веб-приложение. Обработчик этого запроса выглядит следующим образом:

package main

import (
    ...
    jwtware "github.com/gofiber/contrib/jwt"
    jwt "github.com/golang-jwt/jwt/v5"
    ...
)

const (
    contextKeyUser = "user"
)

func main() {
    app := fiber.New()

    ...

    // Группа обработчиков, которые требуют авторизации
    authorizedGroup := app.Group("")
    authorizedGroup.Use(jwtware.New(jwtware.Config{
     SigningKey: jwtware.SigningKey{
      Key: jwtSecretKey,
     },
     ContextKey: contextKeyUser,
    }))
    authorizedGroup.Get("/profile", userHandler.Profile)

    logrus.Fatal(app.Listen(":80"))
}

// Структура HTTP-ответа с информацией о пользователе
type ProfileResponse struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

func jwtPayloadFromRequest(c *fiber.Ctx) (jwt.MapClaims, bool) {
    jwtToken, ok := c.Context().Value(contextKeyUser).(*jwt.Token)
    if !ok {
        logrus.WithFields(logrus.Fields{
            "jwt_token_context_value": c.Context().Value(contextKeyUser),
        }).Error("wrong type of JWT token in context")
        return nil, false
    }

    payload, ok := jwtToken.Claims.(jwt.MapClaims)
    if !ok {
        logrus.WithFields(logrus.Fields{
            "jwt_token_claims": jwtToken.Claims,
        }).Error("wrong type of JWT token claims")
        return nil, false
    }

    return payload, true
}

// Обработчик HTTP-запросов на получение информации о пользователе
func (h *UserHandler) Profile(c *fiber.Ctx) error {
    jwtPayload, ok := jwtPayloadFromRequest(c)
    if !ok {
        return c.SendStatus(fiber.StatusUnauthorized)
    }

    userInfo, ok := h.storage.users[jwtPayload["sub"].(string)]
    if !ok {
        return errors.New("user not found")
    }

    return c.JSON(ProfileResponse{
        Email: userInfo.Email,
        Name:  userInfo.Name,
    })
}

Так как проверка авторизации может происходить во многих HTTP-обработчиках, мы вынесли ее на уровень посредников. В микрофреймворке Fiber для этого есть готовый посредник — jwtware.

При инициализации посредника мы указали два свойства:

  • SigningKey — секретный ключ JWT-токена
  • ContextKey — название поля, по которому хранится объект JWT-токена авторизованного пользователя. Этот объект можно использовать в любом обработчике группы authorizedGroup

Когда приходит HTTP-запрос на получение информации о пользователе, веб-приложение проверяет, что в заголовке Authorization указан корректный JWT-токен. Если проверка прошла успешно, веб-приложение ищет пользователя в хранилище по электронной почте, которая записана в полезной нагрузке JWT-токена. Если пользователь был найден, то авторизация пройдена, и мы возвращаем в HTTP-ответе информацию об этом пользователе.

Мы реализовали все части аутентификации и авторизации нашей социальной сети. Теперь соберем все вместе и проверим, что веб-приложение работает.

Проверяем веб-приложение

Полный код веб-приложения выглядит следующим образом:

package main

import (
    "errors"
    "fmt"
    "github.com/gofiber/fiber/v2"
    jwtware "github.com/gofiber/contrib/jwt"
    jwt "github.com/golang-jwt/jwt/v5"
    "github.com/sirupsen/logrus"
    "time"
)

const (
    contextKeyUser = "user"
)

func main() {
    app := fiber.New()

    authStorage := &AuthStorage{map[string]User{}}
    authHandler := &AuthHandler{storage: authStorage}
    userHandler := &UserHandler{storage: authStorage}

    // Группа обработчиков, которые доступны неавторизованным пользователям
    publicGroup := app.Group("")
    publicGroup.Post("/register", authHandler.Register)
    publicGroup.Post("/login", authHandler.Login)

    // Группа обработчиков, которые требуют авторизации
    authorizedGroup := app.Group("")
    authorizedGroup.Use(jwtware.New(jwtware.Config{
        SigningKey: jwtware.SigningKey{
        Key: jwtSecretKey,
        },
        ContextKey: contextKeyUser,
    }))
    authorizedGroup.Get("/profile", userHandler.Profile)

    logrus.Fatal(app.Listen(":80"))
}

type (
    // Обработчик HTTP-запросов на регистрацию и аутентификацию пользователей
    AuthHandler struct {
        storage *AuthStorage
    }

    // Хранилище зарегистрированных пользователей
    // Данные хранятся в оперативной памяти
    AuthStorage struct {
        users map[string]User
    }

    // Структура данных с информацией о пользователе
    User struct {
        Email    string
        Name     string
        password string
    }
)

// Структура HTTP-запроса на регистрацию пользователя
type RegisterRequest struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

// Обработчик HTTP-запросов на регистрацию пользователя
func (h *AuthHandler) Register(c *fiber.Ctx) error {
    regReq := RegisterRequest{}
    if err := c.BodyParser(&regReq); err != nil {
        return fmt.Errorf("body parser: %w", err)
    }

    // Проверяем, что пользователь с таким email еще не зарегистрирован
    if _, exists := h.storage.users[regReq.Email]; exists {
        return errors.New("the user already exists")
    }

    // Сохраняем в память нового зарегистрированного пользователя
    h.storage.users[regReq.Email] = User{
        Email:    regReq.Email,
        Name:     regReq.Name,
        password: regReq.Password,
    }

    return c.SendStatus(fiber.StatusCreated)
}

// Структура HTTP-запроса на вход в аккаунт
type LoginRequest struct {
    Email    string `json:"email"`
    Password string `json:"password"`
}

// Структура HTTP-ответа на вход в аккаунт
// В ответе содержится JWT-токен авторизованного пользователя
type LoginResponse struct {
    AccessToken string `json:"access_token"`
}

var (
    errBadCredentials = errors.New("email or password is incorrect")
)

// Секретный ключ для подписи JWT-токена
// Необходимо хранить в безопасном месте
var jwtSecretKey = []byte("very-secret-key")

// Обработчик HTTP-запросов на вход в аккаунт
func (h *AuthHandler) Login(c *fiber.Ctx) error {
    regReq := LoginRequest{}
    if err := c.BodyParser(&regReq); err != nil {
        return fmt.Errorf("body parser: %w", err)
    }

    // Ищем пользователя в памяти приложения по электронной почте
    user, exists := h.storage.users[regReq.Email]
    // Если пользователь не найден, возвращаем ошибку
    if !exists {
        return errBadCredentials
    }
    // Если пользователь найден, но у него другой пароль, возвращаем ошибку
    if user.password != regReq.Password {
        return errBadCredentials
    }

    // Генерируем JWT-токен для пользователя,
    // который он будет использовать в будущих HTTP-запросах

    // Генерируем полезные данные, которые будут храниться в токене
    payload := jwt.MapClaims{
        "sub": user.Email,
        "exp": time.Now().Add(time.Hour * 72).Unix(),
    }

    // Создаем новый JWT-токен и подписываем его по алгоритму HS256
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)

    t, err := token.SignedString(jwtSecretKey)
    if err != nil {
        logrus.WithError(err).Error("JWT token signing")
        return c.SendStatus(fiber.StatusInternalServerError)
    }

    return c.JSON(LoginResponse{AccessToken: t})
}

// Обработчик HTTP-запросов, которые связаны с пользователем
type UserHandler struct {
    storage *AuthStorage
}

// Структура HTTP-ответа с информацией о пользователе
type ProfileResponse struct {
    Email string `json:"email"`
    Name  string `json:"name"`
}

func jwtPayloadFromRequest(c *fiber.Ctx) (jwt.MapClaims, bool) {
    jwtToken, ok := c.Context().Value(contextKeyUser).(*jwt.Token)
    if !ok {
        logrus.WithFields(logrus.Fields{
            "jwt_token_context_value": c.Context().Value(contextKeyUser),
        }).Error("wrong type of JWT token in context")
        return nil, false
    }

    payload, ok := jwtToken.Claims.(jwt.MapClaims)
    if !ok {
        logrus.WithFields(logrus.Fields{
            "jwt_token_claims": jwtToken.Claims,
        }).Error("wrong type of JWT token claims")
        return nil, false
    }

    return payload, true
}

// Обработчик HTTP-запросов на получение информации о пользователе
func (h *UserHandler) Profile(c *fiber.Ctx) error {
    jwtPayload, ok := jwtPayloadFromRequest(c)
    if !ok {
        return c.SendStatus(fiber.StatusUnauthorized)
    }

    userInfo, ok := h.storage.users[jwtPayload["sub"].(string)]
    if !ok {
        return errors.New("user not found")
    }

    return c.JSON(ProfileResponse{
        Email: userInfo.Email,
        Name:  userInfo.Name,
    })
}

Запускаем веб-приложение и отправляем запрос на регистрацию нового пользователя:

curl --location --request POST 'http://localhost/register' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "john@doe.com",
    "name": "John",
    "password": "pickles"
}'

В ответ получаем:

HTTP/1.1 201 Created

Это означает, что такого пользователя нет в хранилище, и он был успешно создан.

Теперь попробуем пройти аутентификацию этого пользователя:

curl --location --request POST 'http://localhost/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "john@doe.com",
    "password": "pickles"
}'

В ответ приходит:

HTTP/1.1 200 OK

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg5NTEwMTcsInN1YiI6ImpvaG5AZG9lLmNvbSJ9.Q3k6yMFYtuzPyjoZYpIHibJQPey29QWmlHfwS2A3keM"}

Мы указали корректные учетные данные пользователя, поэтому аутентификация прошла успешно. В ответ веб-приложение вернуло JWT-токен, который мы будем использовать для авторизации при получении информации о пользователе.

Отправляем запрос на получение информации о пользователе:

curl -v 'http://localhost/profile' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg5NTE0NDEsInN1YiI6ImpvaG5AZG9lLmNvbSJ9.e4yIoGzQC8ckcRISBjt4g18S2VEBiHrRhXG7N39-7qI'

В ответ приходит:

HTTP/1.1 200 OK

{"email":"john@doe.com","name":"John"}

Так как мы передали JWT-токен в заголовке HTTP-запроса Authorization, веб-приложение авторизовало нас как пользователя john@doe.com, и вернуло информацию о нашем аккаунте.

В последнем запросе есть один интересный момент. Мы поставили перед значением токена слово Bearer:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2Njg5NTE0NDEsInN1YiI6ImpvaG5AZG9lLmNvbSJ9.e4yIoGzQC8ckcRISBjt4g18S2VEBiHrRhXG7N39-7qI

Bearer переводится как носитель. Он дает веб-приложению понять, что в заголовке Authorization передан токен для авторизации. Принято считать, что это слово означает фразу: «Дай доступ к носителю этого токена». Если не указать слово Bearer перед значением, то авторизация не пройдет даже с корректным значением токена.

Мы реализовали функцию регистрации, аутентификации и получение информации об аккаунте авторизованного пользователя. Для авторизации мы использовали JWT-токен, который генерируются при входе в аккаунт на 72 часа и передается в заголовке HTTP-запроса Authorization.

Выводы

  • Аутентификация — это процесс проверки подлинности пользователя, который пытается получить доступ к веб-приложению
  • Авторизация — это процесс проверки прав пользователя на какое-либо действие в веб-приложении
  • Один из способов реализации системы авторизации — это JWT-токен. Токен генерируется и подписывается веб-приложением после успешной аутентификации и передается во всех последующих HTTP-запросах в заголовке Authorization
  • JWT-токен содержит информацию о пользователе и подпись, которая необходима для авторизации. Так как токен подписан секретным ключом в веб-приложении, то его может проверять на подлинность только это веб-приложение.

Дополнительные материалы

  1. JWT
  2. Fiber JWT Middleware
  3. JWT-авторизация и сессии

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»
Изображение Тото

Задавайте вопросы, если хотите обсудить теорию или упражнения. Команда поддержки Хекслета и опытные участники сообщества помогут найти ответы и решить задачу