Безопасна ли эта реализация djoser?
Этот вопрос может быть слишком широким для StackOverflow, но я не уверен, куда еще можно обратиться за помощью.
Я написал простую систему аутентификации на Django с помощью Djoser и его реализации JWT, используя jQuery на фронтенде. Я действительно не знаю, что я делаю, и я на 99% уверен, что я сделал это неправильно и это абсолютно небезопасно.
Сначала, когда пользователь отправляет форму входа, я посылаю POST-запрос для получения маркера обновления и маркера доступа. Токен обновления хранится в cookie, а токен доступа - в sessionStorage:
// Post the form
$.post("/auth/jwt/create/", $(this).serialize())
// Success: store tokens & redirect
.done(function(data) {
// Logged in: set redirect path & store tokens
if (data.refresh !== "undefined" && data.access !== "undefined") {
Cookies.set("refresh_token", data.refresh, { expires: 30, secure: true, sameSite: "strict" });
sessionStorage.setItem("access_token", data.access);
}
})
У меня есть еще один простой скрипт, который запускается каждый раз, когда загружается страница. В нем я проверяю токен доступа, пытаюсь обновить его, если он недействителен, получаю данные пользователя, используя токен доступа, а затем отправляю эти данные пользователя на бэкэнд для входа в систему. Этот скрипт также выводит пользователя из системы, если он находится на странице выхода:
// Log in or out
function auth(data) {
$.post("/auth/", {
"user": data,
"csrfmiddlewaretoken": $("meta[name='csrf-token']").attr("content"),
});
}
// Remove tokens & log out
function logout() {
Cookies.remove("refresh_token");
sessionStorage.removeItem("access_token");
auth("");
}
// Authorize: get user data & log in
function authorize() {
$.ajax({
url: "/auth/users/me/",
headers: { "Authorization": "JWT "+sessionStorage.getItem("access_token") },
})
// Success: log in
.done(function(data) { auth(JSON.stringify(data)); })
// Fail: log out
.fail(function() { logout(); });
}
// Verify access token & authorize
function verify() {
$.post("/auth/jwt/verify/", { "token": sessionStorage.getItem("access_token") })
// Success: authorize
.done(function() { authorize(); })
// Fail: refresh access token
.fail(function() {
$.post("/auth/jwt/refresh/", { "refresh": Cookies.get("refresh_token") })
// Success: store new access token & authorize
.done(function(data) {
sessionStorage.setItem("access_token", data.access);
authorize();
})
// Fail: log out
.fail(function() { logout(); });
});
}
// Log out page
if (window.location.pathname == "/logout/") {
logout();
}
// Attempt login
else verify();
Наконец, на бэкенде я регистрирую пользователя в системе или из нее с помощью встроенных в Django login
и logout
:
def auth(request):
if not request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest' or not request.method=="POST":
return HttpResponseNotAllowed(["POST"])
user_post = request.POST.get("user")
user = None
if user_post != "":
user_post = json.loads(user_post)
if "id" in user_post and "username" in user_post and "email" in user_post:
user = User.objects.filter(id=user_post["id"], username=user_post["username"], email=user_post["email"]).first()
if user == None:
logout(request)
return HttpResponse("User logged out.")
else:
login(request, user)
return HttpResponse("User logged in.")
Больше всего меня беспокоит та часть, где простой POST-запрос может зарегистрировать пользователя, используя только его id, имя пользователя и электронную почту. Хотя я собираюсь держать идентификатор скрытым от общественности, если хакер каким-то образом получит его (или просто угадает), он сможет легко обойти необходимость в пароле или каких-либо токенах.
Мне кажется, что поток авторизации не совсем правильный. На мой взгляд, это должно работать следующим образом: у вас есть конечная точка входа, пользователь вводит свои учетные данные, например, email и пароль. Вы проверяете пользователя в базе данных и, если все в порядке, возвращаете access_token
и refresh_token
. Мне также кажется более логичным, что сервер должен устанавливать cookies с маркерами, например, через свои JWTTokensMiddleware
или просто в конечной точке.
Для безопасности - refresh_token
куки будут храниться с установленным флагом httponly=True
, чтобы к ним вообще нельзя было получить доступ из js
кода; access_token
куки будут иметь флаг httponly=False
, в свою очередь вы будете читать эти куки и устанавливать их в Authorization
заголовок. Некоторые люди хранят access_token
в localStore
- это тоже вариант, у любого подхода есть свои плюсы и минусы.
Соответственно, если пользователь решит выйти из системы, бэкенд удалит куки с токенами. Чтобы выйти из системы, необходимо предоставить действительный access_token
через заголовок, и чтобы действительный refresh_token
был в куках.
Мне также кажется, что проверка токенов также должна выполняться сервером, по крайней мере, для проверки access_token
вам нужен секретный ключ, и было бы гораздо надежнее хранить его на сервере, а не на клиенте.
Например, для этого можно было бы использовать пользовательские JWTMiddleware
, что-то вроде того, как Django
использует SessionMiddleware
в сочетании с AuthenticationMiddleware
.
На клиенте единственное, что вы проверяете, это access_token
, а точнее дату истечения срока действия, если срок действия токена истек, вы отправляете запрос на конечную точку refresh_token
и, если все в порядке, получаете новый access_token
, или новую пару access_token
+ refresh_token
, в зависимости от того, как вы решите это настроить.
Кроме того, у меня, например, refresh_token
- это не jwt
, а надежная случайная строка достаточной длины. refresh_token
, в моем случае, хранится в базе данных, в хешированном виде, без использования соли, и является одноразовой, то есть, когда клиент вызывает конечную точку - /api/token-refresh/
, всегда возвращается новая пара - access_token
+ refresh_token
, а старая refresh_token
считается недействительной.
p.s. Возможно, вам нужно больше деталей, или я недостаточно раскрыл что-то, дайте мне знать в комментариях, и я постараюсь дать больше подробностей. Также, это не призыв к действию, не думайте, что вы должны делать только так и никак иначе, просто мои мысли и опыт реализации подобного потока аутентификации.