Мне было поручено создать веб-интерфейс для приложения Android на основе Firebase. У меня есть несколько конечных точек, которые взаимодействуют с базой данных (облачные функции). Чтобы получить доступ к этим конечным точкам, мне нужно аутентифицировать пользователя по электронной почте и паролю [1], получить accessToken [2] и авторизовать каждый запрос к конечным точкам с заголовком Authorization: Bearer {accessToken}
.
Я использую php и пытаюсь обдумать, как управлять аутентифицированным пользователем в моем приложении.
TL; DR, пожалуйста, смотрите мое окончательное решение только в php. https://stackoverflow.com/a/52119600/814031
Я передаю accessToken через ajax в сеансе php, чтобы подписать запросы cURL к конечным точкам. По-видимому, нет другого пути, чем использовать firebase JS auth (не так, как я понимаю [4]).
Мой вопрос: Достаточно ли сохранить accessToken
в сеансе php и сравнить его с каждой загрузкой страницы с помощью ajax-запроса POST (см. код ниже)? Что будет более надежной стратегией для обработки этого в php?
Изменить: Пользователь указал, что использование классических php-сессий с токенами JWT не очень много смысл, и я прочитал об этой теме. Что касается Firebase - это что-то рассмотреть? https://firebase.google.com/docs/auth/admin/manage- печенье
Firebase Auth обеспечивает управление файлами cookie сеансов на стороне сервера для традиционных веб-сайтов, которые используют файлы cookie сеансов. Это решение имеет несколько преимуществ по сравнению с короткоживущими токенами ID на стороне клиента, которые могут потребовать каждый раз механизм перенаправления для обновления cookie сеанса по истечении срока действия:
Вот что у меня получилось:
< Сильный > 1 . Страница входа
Как описано в примерах Firebase [3]
function initApp() {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
// User is signed in.
// obtain token, getIdToken(false) = no forced refresh
firebase.auth().currentUser.getIdToken(false).then(function (idToken) {
// Send token to your backend via HTTPS
$.ajax({
type: 'POST',
url: '/auth/check',
data: {'token': idToken},
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
});
// ...
}).catch(function (error) {
console.log(error);
});
} else {
// User Signed out
$.ajax({
type: 'POST',
url: '/auth/logout',
complete: function(data){
// data = {'target' => '/redirect/to/route'}
if(getProperty(data, 'responseJSON.target', false)){
// don't redirect to itself
// logout => /
if(window.location.pathname != getProperty(data, 'responseJSON.target', false)){
window.location.replace(getProperty(data, 'responseJSON.target'));
}
}
}
});
// User is signed out.
}
});
}
window.onload = function () {
initApp();
};
< Сильный > 2 . PHP-контроллер для обработки запросов на аутентификацию
public function auth($action)
{
switch($action) {
// auth/logout
case 'logout':
unset($_SESSION);
// some http status header and mime type header
echo json_encode(['target' => '/']); // / => index page
break;
case 'check':
// login.
if(! empty($_POST['token']) && empty($_SESSION['token'])){
// What if I send some bogus data here? The call to the Endpoint later would fail anyway
// But should it get so far?
$_SESSION['token'] = $_POST['token'];
// send a redirect target back to the JS
echo json_encode(['target' => '/dashboard']);
break;
}
if($_POST['token'] == $_SESSION['token']){
// do nothing;
break;
}
break;
}
}
< Сильный > 3 . Главный контроллер
// pseudo code
class App
{
public function __construct()
{
if($_SESSION['token']){
$client = new \GuzzleHttp\Client();
// $user now holds all custom access rights within the app.
$this->user = $client->request(
'GET',
'https://us-centralx-xyz.cloudfunctions.net/user_endpoint',
['headers' =>
[
'Authorization' => "Bearer {$_SESSION['token']}"
]
]
)->getBody()->getContents();
}else{
$this->user = null;
}
}
public function dashboard(){
if($this->user){
var_dump($this->user);
}else{
unset($_SESSION);
// redirect to '/'
}
}
}
Спасибо за ваше время!
- [1]: https://firebase.google.com/docs/auth/ Web / пароля аутентификации
- [2]: https://firebase.google.com/docs/ ссылка / JS / firebase.User # getIdToken
- [3]: https://github.com/ firebase / Быстрый старт -JS / BLOB / Master / авт / электронный - password.html
- [4]: https://github.com/kreait/firebase- PHP / вопросы / 159 # issuecomment - 360225655
3 ответа
Я должен признать, что сложность документации, примеров и различных сервисов Firebase привела меня в замешательство, и я подумал, что аутентификация в Интернете возможна только через JavaScript. Это было неправильно. По крайней мере, для моего случая, когда я просто войду в систему с помощью электронной почты и пароля , чтобы получить токен Json Web (JWT) , чтобы подписать все вызовы облачных функций Firebase. Вместо того, чтобы манипулировать странными запросами Ajax или устанавливать cookie-токен с помощью JavaScript, мне просто нужно было вызвать API REST Firebase Auth
Вот минимальный пример использования Fatfreeframework:
Форма входа
<form action="/auth" method="post">
<input name="email">
<input name="password">
<input type="submit">
</form>
Маршрут
$f3->route('POST /auth', 'App->auth');
Контроллер
class App
{
function auth()
{
$email = $this->f3->get('POST.email');
$password = $this->f3->get('POST.password');
$apiKey = 'API_KEY'; // see https://firebase.google.com/docs/web/setup
$auth = new Auth($apiKey);
$result = $auth->login($email,$password);
if($result['success']){
$this->f3->set('COOKIE.token',$result['idToken']);
$this->f3->reroute('/dashboard');
}else{
$this->f3->clear('COOKIE.token');
$this->f3->reroute('/');
}
}
}
< Сильный > Класс сильный >
<?php
use GuzzleHttp\Client;
class Auth
{
protected $apiKey;
public function __construct($apiKey){
$this->apiKey = $apiKey;
}
public function login($email,$password)
{
$client = new Client();
// Create a POST request using google api
$key = $this->apiKey;
$responsee = $client->request(
'POST',
'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=' . $key,
[
'headers' => [
'content-type' => 'application/json',
'Accept' => 'application/json'
],
'body' => json_encode([
'email' => $email,
'password' => $password,
'returnSecureToken' => true
]),
'exceptions' => false
]
);
$body = $responsee->getBody();
$js = json_decode($body);
if (isset($js->error)) {
return [
'success' => false,
'message' => $js->error->message
];
} else {
return [
'success' => true,
'localId' => $js->localId,
'idToken' => $js->idToken,
'email' => $js->email,
'refreshToken' => $js->refreshToken,
'expiresIn' => $js->expiresIn,
];
}
}
}
Вы действительно не должны использовать сессии в PHP при использовании токенов. Токены должны отправляться в заголовке при каждом запросе (или файл cookie тоже работает).
Токены работают следующим образом: 1. Вы входите в систему, сервер копирует токен с некоторой закодированной информацией. 2. Вы отправляете этот токен обратно при каждом запросе.
На основании информации, закодированной в токене, сервер может получить информацию о пользователе. Обычно в нем кодируется какой-то идентификатор пользователя. Сервер знает, что это действительный токен из-за способа его кодирования.
Отправляйте токен на каждый запрос, который вам нужно сделать, затем в PHP вы можете просто передать этот токен другому API
Похоже, @Chad K выводит вас на правильный путь (куки и ajax - завтрак чемпионов ... :), хотя я думал поделиться своим кодом из моей рабочей системы (с некоторыми вещами о конфиденциальности, конечно!)
Ищите комментарии типа / **** для вещей, которые вам нужно настроить самостоятельно (возможно, вы захотите сделать и другие вещи из Firebase по-другому - см. Документы ...)
Страница LOGIN.php (я считаю, что проще держать это отдельно - см. Примечания, чтобы узнать, почему ....)
<script>
/**** I picked this up somewhere off SO - kudos to them - I use it a lot!.... :) */
function setCookie(name, value, days = 7, path = '/') {
var expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + expires + '; path=' + path;
}
function getCookie(c_name) {
if (document.cookie.length > 0) {
c_start = document.cookie.indexOf(c_name + "=");
if (c_start !== -1) {
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end === -1) {
c_end = document.cookie.length;
}
return unescape(document.cookie.substring(c_start, c_end));
}
}
return "";
}
</script>
<script>
var config = {
apiKey: "your_key",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
projectId: "myapp",
storageBucket: "myapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
</script>
<script src="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.js"></script>
<link type="text/css" rel="stylesheet" href="https://cdn.firebase.com/libs/firebaseui/2.7.0/firebaseui.css"/>
<script type="text/javascript">
/**** set this url to the 'logged in' page (mine goes to a dashboard) */
var url = 'https://my.app/index.php#dashboard';
/**** by doing this signOut first, then it is simple to send any 'logout' request in the app to 'login.php' - one page does it.... :) */
firebase.auth().signOut().then(function () {
}).catch(function (error) {
console.log(error);
});
var signInFlow = 'popup';
if (('standalone' in window.navigator)
&& window.navigator.standalone) {
signInFlow = 'redirect';
}
var uiConfig = {
callbacks: {
signInSuccessWithAuthResult: function (authResult, redirectUrl) {
/**** here you can see the logged in user */
var firebaseUser = authResult.user;
var credential = authResult.credential;
var isNewUser = authResult.additionalUserInfo.isNewUser;
var providerId = authResult.additionalUserInfo.providerId;
var operationType = authResult.operationType;
/**** I like to force emailVerified...... */
if (firebaseUser.emailVerified !== true) {
firebase.auth().currentUser.sendEmailVerification().then(function () {
/**** if using this, you can set up your own usermgmt.php page for the user verifications (see firebase docs) */
window.location.replace("https://my.app/usermgmt.php?mode=checkEmail");
}).catch(function (error) {
console.log("an error has occurred in sending verification email " + error)
});
}
else {
var accessToken = firebaseUser.qa;
/**** set the Cookie (yes, I found this best, too) */
setCookie('firebaseRegistrationID', accessToken, 1);
/**** set up the AJAX call to PHP (where you will store this data for later lookup/processing....) - I use "function=....." and "return=....." to have options for all functions and what to select for the return so that ajax.php can be called for 'anything' (you can just call a special page if you like instead of this - if you use this idea, be sure to secure the ajax.php 'function' call to protect from non-authorized use!) */
var elements = {
function: "set_user_data",
user: JSON.stringify(firebaseUser),
return: 'page',
accessToken: accessToken
};
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
/**** this calls ajax and gets the 'page' to set (this is from a feature where I store the current page the user is on, then when they log in again here, we go back to the same page - no need for cookies, etc. - only the login cookie is needed (and available for 'prying eyes' to see!) */
url = 'index.php#' + data;
var form = $('<form method="post" action="' + url + '"></form>');
$('body').append(form);
form.submit();
});
}
return false;
},
signInFailure: function (error) {
console.log("error - signInFailure", error);
return handleUIError(error);
},
uiShown: function () {
var loader = document.getElementById('loader');
if (loader) {
loader.style.display = 'none';
}
}
},
credentialHelper: firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM,
queryParameterForWidgetMode: 'mode',
queryParameterForSignInSuccessUrl: 'signInSuccessUrl',
signInFlow: signInFlow,
signInSuccessUrl: url,
signInOptions: [
firebase.auth.GoogleAuthProvider.PROVIDER_ID,
// firebase.auth.FacebookAuthProvider.PROVIDER_ID,
// firebase.auth.TwitterAuthProvider.PROVIDER_ID,
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: true,
customParameters: {
prompt: 'select_account'
}
}
/* {
provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID,
// Invisible reCAPTCHA with image challenge and bottom left badge.
recaptchaParameters: {
type: 'image',
size: 'invisible',
badge: 'bottomleft'
}
}
*/
],
tosUrl: 'https://my.app/login.php'
};
var ui = new firebaseui.auth.AuthUI(firebase.auth());
(function () {
ui.start('#firebaseui-auth-container', uiConfig);
})();
</script>
Теперь, на каждой странице, которую вы хотите, чтобы пользователь видел (в моем случае все это проходит через index.php # что-то - что облегчает .... :)
<script src="https://www.gstatic.com/firebasejs/4.12.0/firebase.js"></script>
<script>
// Initialize Firebase - from https://github.com/firebase/firebaseui-web
var firebaseUser;
var config = {
apiKey: "your_key",
authDomain: "yourapp.firebaseapp.com",
databaseURL: "https://yourapp.firebaseio.com",
projectId: "yourapp",
storageBucket: "yourapp.appspot.com",
messagingSenderId: "the_number"
};
firebase.initializeApp(config);
initFBApp = function () {
firebase.auth().onAuthStateChanged(function (firebaseuser) {
if (firebaseuser) {
/**** here, I have another ajax call that sets up some select boxes, etc. (I chose to call it here, you can call it anywhere...) */
haveFBuser();
firebaseUser = firebaseuser;
// User is signed in.
var displayName = firebaseuser.displayName;
var email = firebaseuser.email;
var emailVerified = firebaseuser.emailVerified;
var photoURL = firebaseuser.photoURL;
if (firebaseuser.photoURL.length) {
/**** set the profile picture (presuming you are showing it....) */
$(".profilepic").prop('src', firebaseuser.photoURL);
}
var phoneNumber = firebaseuser.phoneNumber;
var uid = firebaseuser.uid;
var providerData = firebaseuser.providerData;
var string = "";
firebaseuser.getIdToken().then(function (accessToken) {
// document.getElementById('sign-in-status').textContent = 'Signed in';
// document.getElementById('sign-in').textContent = 'Sign out';
/**** set up another ajax call.... - to store things (yes, again.... - though this time it may be due to firebase changing the token, so we need it twice...) */
string = JSON.stringify({
displayName: displayName,
email: email,
emailVerified: emailVerified,
phoneNumber: phoneNumber,
photoURL: photoURL,
uid: uid,
accessToken: accessToken,
providerData: providerData
});
if (accessToken !== '<?php echo $_COOKIE['firebaseRegistrationID']?>') {
console.log("RESETTING COOKIE with new accessToken ");
setCookie('firebaseRegistrationID', accessToken, 1);
var elements = 'function=set_user_data&user=' + string;
$.ajaxSetup({cache: false});
$.post("data/ajax.php", elements, function (data) {
<?php
/**** leave this out for now and see if anything weird happens - should be OK but you might want to use it (refreshes the page when firebase changes things..... I found it not very user friendly as they reset at 'odd' times....)
/*
// var url = 'index.php#<?php echo(!empty($user->userNextPage) ? $user->userNextPage : 'dashboard'); ?>';
// var form = $('<form action="' + url + '" method="post">' + '</form>');
// $('body').append(form);
// console.log('TODO - leave this form.submit(); out for now and see if anything weird happens - should be OK');
// form.submit();
*/
?>
});
}
});
} else {
console.log("firebase user CHANGED");
document.location.href = "../login.php";
}
}, function (error) {
console.log(error);
}
);
};
window.addEventListener('load', function () {
initFBApp();
});
</script>
Надеюсь это поможет. Это из моей рабочей системы, которая включает в себя некоторые дополнительные функции, которые я добавил туда по пути, но в основном это напрямую из firebase, так что вы должны быть в состоянии следовать достаточно хорошо.
Кажется, гораздо более простой маршрут, чем ваш оригинальный.
Похожие вопросы
Новые вопросы
php
PHP - это широко используемый высокоуровневый, динамический, объектно-ориентированный и интерпретируемый язык сценариев, в первую очередь предназначенный для серверной веб-разработки. Используется для вопросов о языке PHP.