Сеансы проверки подлинности на основе маркеров
Я создаю приложение в PHP Lumen, которое возвращает токен при входе в систему. Я не знаю, как выйти за пределы этого.
Как я должен поддерживать сеанс, используя эти токены?
в частности, как хранить токены на стороне клиента, если я использую reactjs или vanilla HTML/CSS/jQuery и отправляю их в каждом запросе, который я делаю для безопасной части моего веб-приложения?
8 ответов
обычно я храню токен в локальном хранилище, таким образом, я могу сохранить токен, даже если пользователь покидает сайт.
localStorage.setItem('app-token', theTokenFromServer);
каждый раз, когда пользователь загружает страницу, первое, что я сделать, это посмотреть на наличие маркера.
token = localStorage.getItem('app-token');
если использовать react, я бы сохранил токен в глобальном состоянии (например, используя redux):
function loadAppToken(token) {
return {
type: 'LOAD_TOKEN',
payload: { token },
};
}
С vanilla javascript я бы сохранил его в своей утилите подключения. Что может выглядеть следующий:
const token = localStorage.getItem('app-token');
export function request(config) {
const { url, ...others } = config;
return fetch(url, {
...others,
credentials: 'include',
headers: {
'Authorization': `Bearer ${token}`
},
});
}
у меня все еще была бы утилита выборки в приложении react, аналогичная предыдущему коду, но я бы отправил токен в параметрах, получив его в промежуточном по redux для каждого отдельного запроса.
в настоящее время работает над тем же типом приложения, используя lumen для API. Следующие 3 шага для аутентификации на основе токенов в люмен с JWT:
1. Создайте токен и вернитесь после успешного входа в систему
public function login(Request $request) {
$token = $this->jwt->attempt(['user_name' => $data['user_name'], 'password' => $data['password']]); //$token = $this->jwt->attempt($data);
if (!$token) {
$response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_INVALID_USER, 'error' => array(Messages::MSG_INVALID_USER)));
return response()->json($response);
} else {
$user = \Auth::setToken($token)->user();
$data = array('token' => $token,'user_id' => $user->id);
$response = array('success' => true, 'data' => $data, 'detail' => array('message' => Messages::MSG_SUCCESS, 'error' => null));
return response()->json($response);
}
}
2. Определите промежуточное ПО для проверки токенов
public function handle($request, Closure $next, $guard = null) {
try {
$token = $request->header('X-TOKEN');
$user_id = $request->header('X-USER');
$user = \Auth::setToken($token)->user();
if ($user && $user->id == $user_id) {
return $next($request);
} else {
$response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERR_INVALID_TOKEN, 'error' => Messages::MSG_ERR_INVALID_TOKEN));
return response()->json($response);
}
} catch (Exception $ex) {
$response = array('success' => false, 'data' => null, 'detail' => array('message' => Messages::MSG_ERROR_500, 'error' => array($ex)));
return response()->json($response);
}
}
3. Хранить токен в localstorage или в cookies
localStorage.setItem("Token", JSON.stringify(TokenData));
TokenData = JSON.parse(localStorage.getItem("Token"));
или
$.cookie('Token', JSON.stringify(TokenData), {expires: 1, path: '/'});
TokenData = JSON.parse($.cookie("Token"));
4. Отправить токен с каждым запросом в заголовках
запрос с пользовательскими заголовками
$.ajax({
url: 'foo/bar',
headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId}
});
заголовки для каждого запроса
$.ajaxSetup({
headers: { 'X-TOKEN': TokenData.Token ,'X-USER': TokenData.UserId}
});
надеюсь, это поможет.
Примечание: добавить некоторые проверки и проверки данных при чтении данных из localstorage
или cookies
.
предположим,вы хотите создать приложение.
- ReactJS
- REST API с PHP
- используя JWT в
1. Введение
вы должны забыть о сеансах при создании REST API.
REST API предназначены для апатридов, поэтому они не должны зависеть от сеансов, они должны обрабатывать запросы только с данными, предоставленными клиентом.
2. Проверка подлинности
все, что клиент хочет не только обменять username
& password
для маркера.
это пример HTTP-запроса
POST /api/v1/authentication HTTP/1.1
Host: localhost
Content-Type: application/json
{
"username": "foo",
"password": "bar"
}
и ответ:
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
3. давайте более подробно рассмотрим запрос / ответ
как наш API будет обрабатывать запрос Аутентификации?
он будет проверять, если пользователь с именем пользователя
foo
и парольbar
is основан и активен в DBон будет генерировать JWT (JSON Web Token)
он вернет ответ, содержащий JWT
это какой-то супер простой метод auth, например.
public function authAction()
{
/** Get your payload somehow */
$request = $_POST;
//Validate if username & password are given/
$user = $this->model->auth($username, $password);
if(!$user) {
//throw error for not valid credentials
}
$jwt = $this->jwt->create($user);
//return response with $jwt
}
как вы видите, они не установлены сеансы или что-то еще.
как наша клиентская сторона будет обрабатывать ответ?
клиент смог использовать некоторый пакет как суперагент для обработки запросов и ответов на наш API таким образом, процесс будет упрощен до этого:
let data = {
username: email,
password: password
};
request
.post('/api/v1/authentication')
.set('Content-Type', 'application/json')
.send(data)
.end(function (error, response) {
//response.body.token
});
4. Создание JWT на стороне сервера
вы можете использовать какой-то 3-й пакет PT для формирование и проверка JWT вместо того, чтобы писать его самостоятельно.
смотреть на это пакета, вы можете видеть, как это делается.
и не забудьте всегда создавать сильные подписи.
Я рекомендуем использовать RSA keys
Я не рекламирую или поддерживаю этот проект, просто нашел полезным поделиться им здесь. Я никогда не использовал его, я использую что-то подобное в своих проектах NodeJS.
5. Сохранение JWT на стороне клиента
они два пути, как вы уже знаете localStorage
& cookies
Для меня я использую cookies, потому что:
- они немного больше безопасное.
- истекает дата можно установить без реализации какой-либо пользовательской логики.
- поддержка старых браузеров (очень старые браузеры, поэтому это не так важно).
но все зависит от вас.
6. Используя JWT в
отныне каждый запрос на сервер вы должны включить свой JWT.
в вашем REST API вы должны написать метод для проверки JWT и обмена его на объект пользователя.
пример запрос:
let jwt = ...; //GET IT FROM LOCALSTORAGE OR COOKIE
request
.get('/api/v1/posts')
.set('Content-Type', 'application/json')
.set('Authorization', jwt)
.end(function (error, response) {
});
как API будет обрабатывать этот запрос
public function postsAction()
{
$jwt = $this->headers->get('Authorization');
if(!$this->jwt->validate($jwt)) {
//throw unauthorized error
}
$user = $this->model->exchangeJWT($jwt);
//Your logic here
}
7. Дата истечения срока действия & cookie
если вы используете cookie для сохранения JWT, будьте осторожны с установкой дат истечения срока действия.
на срок годности печенья должна быть равна к JWT срок действия.
вы можете сохранить его в localStorage браузера, а затем установить его в заголовке для каждого запроса на сервер.
вам на самом деле не нужны никакие ReactJS или VanillaJS. Просто чистый HTML и PHP на самом деле. Я просто храню его как печенье.
прежде всего, когда вы получаете токен от Lumen, сохраните его в своей базе данных для конкретного пользователя. Затем установите id пользователя и accesstoken как cookies, срок действия которых истекает через определенное время с помощью этого кода:
setcookie('userid',$userid, time()+(3600 * 24 * 15),"/");
setcookie('accesstoken',$accesstoken, time()+(3600 * 24 * 15),"/");
header('Location: /home.php');
//You can change the 15 in setcookie() to amount of days the cookie will expire in.
//The "/" in setcookie is important, because it ensures the cookies will be available on every page the user visits on your website.
//The header function redirects to your home page after log in
затем ниже, как будет выглядеть ваша домашняя страница. Он проверяет, существует ли accessToken cookie, если это так, он дважды проверяет, что токен соответствует текущему токену в базе данных пользователей. Если это совпадение, он показывает страницу "logged in". Если нет, вы должны показать/перенаправить на страницу входа в систему.
<?php
if (isset($_COOKIE['accesstoken']))
{
//connect to your user database and check that the cookie accesstoken matches
// if it doesn't match, deal with it appropriately, such as deleting all cookies then redirecting to login page.
}
?>
<!DOCTYPE HTML>
<html>
<head>
<title>Sup</title>
</head>
<body>
<?php if (isset($_COOKIE['accesstoken'])){ ?>
<h1>User logged in!</h1>
<h3>Do whatever you need to do if user is logged in</h3>
<?php } else { ?>
<h1>No accesstoken found</h1>
<h3>More than likely you will want to show login page here</h3>
<?php } ?>
</body>
</html>
и тогда выход прост. Приведенный ниже код удаляет accesstokens, установив для них значение expired:
setcookie("accesstoken", "", time() - 3600);
setcookie("userid", "", time() - 3600);
header('Location: /youareloggedout.html');
помните, что это основы функциональной системы входа / выхода из системы. Если бы я объяснил все необходимые меры безопасности, этот пост был бы еще длиннее. Обязательно сделайте свое исследование. Некоторые темы для начала подготовлены заявления и предотвращение XSS-атак. :)
для шифрования и дешифрования вы можете использовать в построенной модели крипты laravel
использовать подсветка\поддержка\фасады\крипта;
то, что мы делаем для генерации маркера API, будет принимать массив обязательных полей.
давайте создадим сведения
$data = [
'user_id' => $user->id,
'time_stemp' => \Carbon::now() // Carbon is laravel's time model(class) for managing times
'expire_on' => \Carbon::now()->addDays(2); //here i'm setting token expires time for 2 days you can change any
];
$data = serialize($data);
затем зашифруйте свои данные с помощью Crypt
$accessToken = Crypt::encrypt($data);
теперь отправить на передний конец в ответ и сохранить в локальном хранилище или cookie ничего не нужно время здесь проверят только на сервере.
теперь в каждом запросе передайте этот токен и на стороне сервера создайте одну среднюю посуду, которая будет анализировать ваши данные, и если ваше время токена меньше, то истекает время, а затем двигаться вперед еще отправить ошибку 403 или что-нибудь вы хотите.
как анализировать данные на стороне сервера
создать промежуточное ПО с помощью команды : php artisan make: middleware ApiAuth тогда часть ручки
//Accesstoken you passed in $headers or in $request param use whatever you like
$searilizerData = Crypt::decrypt($headers['AccessToken']);
$data = unserialize($searilizerData);
//check if expire_on is less then current server time
if($data['expire_on] <= \Curbon::now()){
next(); // let them contuine and access data
} else {
throw new Exception ("Your token has expired please regenerate your token",403);
}
Надежда это поможет :)
Я запишу быстрый todo и лучшие практики, так как есть много способов сделать это с кодом.
бэкэнд
- (POST) маршрут входа {email, пароль} он создаст токен. Вы можете использовать JWT (JSON Web Token) Маркер будет возвращен клиенту. Внутри токена можно сохранить некоторые основные сведения: идентификатор пользователя, имя пользователя, срок действия токена, тип пользователя и т. д. https://jwt.io/
клиент
-
Регистрация запрос, pass {email, пароль}.
при успехе получите токен и сохраните его локально, localstorage предпочтительнее, но cookie также возможен.
-
при каждой загрузке страницы с помощью приложения react у вас должна быть функция проверки этого токена, она расшифрует его и получит сведения для дальнейшего использования.
Я имею в виду получить имя пользователя, идентификатор пользователя и т. д. Более важным, если вы хотите добавить его, является "срок действия", если токен истек, вы перенаправляете пользователя на страницу входа, или вы можете повторно запросить новый токен, это действительно зависит от вашего приложения.
выход из системы, довольно проста... просто удалите токен со стороны клиента и перенаправьте на страницу входа в систему.
убедитесь, что для" аутентифицированных " страниц вы проверяете, что токен существует, и даже дальше вы можете проверить тип пользователя.
* * для расшифровывать стороны клиента JWT, вы можете использовать: https://www.npmjs.com/package/jwt-client
недавно я закончил веб-портал react, где мы использовали JWT для инициирования, обслуживания и истечения срока действия сеанса пользователя.
- при входе в систему, отправка учетных данных пользователя для входа в API. После успеха получите токен обратно из back-end API. Back-end поддерживает генерацию и срок действия токена.
- храните токен в состоянии react (мы используем Redux store) и в хранилище сеансов (в случае обновления страницы мы можем вернуть его из сеанса хранение.)
- (необязательный) запустите счетчик в секунду в хранилище сеансов (чтобы проверить, как долго пользователь простаивает)
- после входа в систему каждый вызов API требует, чтобы маркер был отправлен в заголовке. Вызовы API выполняются с помощью fetch. Если вызов API успешен, мы получаем токен обратно из back-end и заменяем его существующим токеном (оставайтесь свежими).
- все вызовы API являются "fetch" через общую функцию customFetch. Идея иметь общий выборки, чтобы увидеть, если ответ 401 (доступ запрещен). Если это 401, токен истек или недействителен (пользователь пытается получить доступ к чему-то без входа в систему). В этом случае мы выбрасываем пользователя из портала, обратно на логин/домашнюю страницу (отображается ошибка, что доступ запрещен).
- (необязательный) если пользователь простаивает слишком долго (проверка второго счетчика > 900 т. е. 15 мин), мы показываем предупреждение пользователю, что сеанс истекает, дает пользователю возможность продолжить. Если пользователь нажимает кнопку Продолжить, мы вызываем API чтобы снова получить профиль пользователя, убедитесь, что токен по-прежнему действителен. Если API не удался, мы выходим из системы и отправляем обратно на главную страницу. Второй счетчик возвращается к 1 непосредственно перед любым вызовом API (пользователь активен и что-то делает).
- Излишне говорить, что перед отправкой пользователя на вход / домашнюю страницу по любому из вышеперечисленных сценариев мы очищаем хранилище сеансов и сбрасываем состояние (redux store).
- в случае любого обновления происходит, мы получаем токен из хранилища сеансов и отправки начальных действий для создания состояния (Redux store) снова. Если какое-либо из действий (API) не удается, мы показываем пользователю сообщение о том, что сеанс истек или недействителен, и вам нужно войти в систему, отправив пользователя обратно на вход/домашнюю страницу.
фрагменты кода
предположим, что вы извлекли токен из вызова API входа в систему:
установить маркер в хранилище и состоянии сеанса (redux store)
window.sessionStorage.setItem('partyToken', token)
store.dispatch({type: 'profile/setToken', payload: { token }})
маркер извлечения из хранение сеанса или состояние (redux store)
const token = window.sessionStorage.getItem('token')
const token = store.getState().profile && store.getState().profile.token
конечно, вы можете определить общую функцию, где вы можете установить/обновить маркер после каждого вызова API. Аналогично для извлечения, потому что вам нужен токен перед вызовом API.