【问题标题】:Firebase Auth JS/PHPFirebase 身份验证 JS/PHP
【发布时间】:2018-08-21 17:41:17
【问题描述】:

我的任务是为基于 firebase 的 Android 应用构建 Web 界面。 我有一些与数据库交互的端点(云功能)。要访问这些端点,我需要使用电子邮件和密码 [1] 对用户进行身份验证,检索 accessToken[2] 并使用 Authorization: Bearer {accessToken} 标头授权对端点的每个请求。

我使用 php 并且努力思考如何在我的应用程序中管理经过身份验证的用户。

TL;DR 请仅在 php 中查看我的最终解决方案。 https://stackoverflow.com/a/52119600/814031

我在 php 会话中通过 ajax 传输 accessToken,以对端点的 cURL 请求进行签名。 显然,除了使用 firebase JS auth 之外别无他法(据我所知[4])。

我的问题是:accessToken 保存在 php 会话中并通过 ajax POST 请求将其与每个页面加载进行比较是否足够(参见下面的代码)? 在 php 中处理这个问题的更强大的策略是什么?

编辑: A user pointed out 使用带有 JWT 令牌的经典 php 会话没有多大意义,我阅读了有关该主题的信息。 那么关于 Firebase - 这是需要考虑的事情吗? https://firebase.google.com/docs/auth/admin/manage-cookies

Firebase 身份验证为依赖会话 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 '/' 
    }
  }
}

注意:我知道这个 sdk https://github.com/kreait/firebase-php 并且我在那里阅读了很多关于 SO 的问题和帖子,但我很困惑,因为有关于完整管理员权限等的讨论,我真的只与基于 firebase 的端点交互(加上 firebase auth 和 firestore)。而且我还在使用 php 5.6 :-/

感谢您的宝贵时间!

【问题讨论】:

    标签: php ajax firebase firebase-authentication


    【解决方案1】:

    我不得不承认,firebase 文档和示例以及不同服务的复杂性让我很困惑,以至于我认为,网络身份验证只能通过 JavaScript 进行。那是错误的。至少就我而言,我只需使用电子邮件和密码登录检索 Json Web 令牌 (JWT),以签署对 Firebase 云函数的所有调用。无需处理奇怪的 Ajax 请求或通过 JavaScript 设置令牌 cookie,我只需要调用 Firebase Auth REST API

    这是一个使用 Fatfree 框架的最小案例:

    登录表单

    <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,
                ];
    
            }
    
        }
    
    }
    

    Credits

    【讨论】:

    • 我遇到了完全相同的问题 - 这是一个很大的帮助,而且效果很好。如果可以的话,我会投票两次:)谢谢。
    【解决方案2】:

    听起来@Chad K 让你走上了正轨(cookie 和 ajax - 冠军早餐... :),尽管我想从我的工作系统分享我的代码(当然还有一些“隐私”的东西!)

    为您需要自己设置的东西寻找 /**** 类型 cmets(您可能还想以不同的方式做一些其他的 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#something - 这使它更容易...... :)

     <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,因此您应该能够很好地遵循。

    似乎比原来的路线要简单得多。

    【讨论】:

    • 迟到的答案,但你的代码帮助我验证了一些事情,直到我意识到,我可以使用 Firebase Auth REST API 并且不需要任何奇怪的 ajax 调用。
    • 是的,正如我的回答所说,那里有一些“额外”的东西可以进行 ajax 调用以在后端存储数据等 - 但是,如果你只需要 ajax,你就不需要 ajax想要进行身份验证。最初的问题有点“在这里/那里”,因为你不明白你能做什么,这就是我放入“工作代码”示例的原因。很高兴它帮助你找到你需要的东西。请考虑添加一个“它很有用”的点击(我正在和一个朋友竞争“到今年年底在 SO 上的最高分”,所以每一个“它对我有帮助”都会受到赞赏!:)
    【解决方案3】:

    在使用令牌时,您真的不应该在 PHP 中使用会话。应该在每个请求的标头中发送令牌(或者 cookie 也可以)。

    令牌的工作方式如下: 1.您登录,服务器铸造一个带有一些编码信息的令牌 2. 你在每次请求时发回该令牌

    根据令牌中编码的信息,服务器可以获取有关用户的信息。通常,某种用户 ID 被编码在其中。服务器知道它是一个有效的令牌,因为它的编码方式。

    在您需要发出的每个请求上发送令牌,然后在 PHP 中您可以将该令牌传递给其他 API

    【讨论】:

    • 这是一个很好的观点,但我认为通过将令牌存储在会话中,我可以更轻松地从我的 php 应用程序访问,而不是处理那些 ajax 调用。不是?我的意思是我必须存储它。我可以看到,firebase JS SDK 将信息存储在浏览器本地存储中。
    • 如果我将令牌存储从 php 会话交换到 cookie(我可以使用 JS 和 PHP 访问)会更好吗?
    • 要正确使用令牌,您需要在每个 API 调用中包含它。然后,您将拥有与进行会话相同的访问权限。获取令牌就像获取会话一样容易。试试看
    • 令牌可以从前端和后端访问!您可以使用本地存储从 JS 访问令牌,也可以使用 cookie。在我的网站上,我将令牌存储在 cookie 中,因为它更容易处理,并且更容易获取安全图像。这不是推荐的方式,但效果非常好。
    • 感谢@Chad K 让我走上正轨。我现在正在阅读有关 JWT 的更多信息,并且对正在发生的事情以及要寻找的内容有了更好的了解。
    猜你喜欢
    • 1970-01-01
    • 2020-03-31
    • 1970-01-01
    • 2021-10-25
    • 2018-02-04
    • 2021-10-16
    • 1970-01-01
    • 2020-04-01
    相关资源
    最近更新 更多