首页 文章

Firebase Auth JS / PHP

提问于
浏览
1

我有一些与数据库交互的 endpoints ( Cloud 功能) . 要访问这些 endpoints ,我需要使用电子邮件和密码[1]对用户进行身份验证,检索accessToken [2]并使用 Authorization: Bearer {accessToken} 标头对 endpoints 的每个请求进行授权 .

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

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

我在php会话中通过ajax传输accessToken,以便向 endpoints 签署cURL请求 . 显然没有其他方法可以使用firebase JS auth(据我所知[4]而言) .

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

Edit: A user pointed out使用经典的PHP会话与JWT令牌没有多大意义,我读了一下这个主题 . 关于Firebase - 这是需要考虑的事情吗? https://firebase.google.com/docs/auth/admin/manage-cookies

Firebase Auth为依赖会话cookie的传统网站提供服务器端会话cookie管理 . 与客户端短期ID令牌相比,此解决方案有几个优点,每次可能需要重定向机制才能在到期时更新会话cookie:

这是我得到的:

1. Login Page

如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. a php controller to handle the auth requests

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. the Main controller

// 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上的帖子中阅读了很多,但我感到困惑,因为有关于完全管理员权限等的讨论,我真的只与 endpoints 交互构建在firebase上(加上firebase auth和firestore) . 我还在使用php 5.6: - /

谢谢你的时间!

3 回答

  • 0

    我必须承认,firebase文档和示例以及不同服务的复杂性使我感到困惑,我认为,只有通过JavaScript才能对Web进行身份验证 . 那是错的 . 至少在我的情况下,我只需 login with email and passwordretrieve a Json Web Token (JWT) ,即可签署所有对Firebase Cloud 功能的调用 . 而不是通过JavaScript处理奇怪的Ajax请求或设置令牌cookie,我只需要调用Firebase Auth REST API

    以下是使用Fatfreeframework的最小案例:

    Login form

    <form action="/auth" method="post">
        <input name="email">
        <input name="password">
        <input type="submit">
    </form>
    

    Route

    $f3->route('POST /auth', 'App->auth');
    

    Controller

    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('/');
            }
        }
    }
    

    Class

    <?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

  • 0

    在使用令牌时,你真的不应该在PHP中使用会话 . 标记应该在每个请求的头文件中发送(或cookie也可以) .

    令牌的工作方式如下:1 . 您登录时,服务器会根据编码的某些信息创建一个令牌 . 您可以在每次请求时发回该令牌

    基于令牌中编码的信息,服务器可以获得关于用户的信息 . 通常,某种用户ID在其中进行编码 . 服务器知道它是一个有效的令牌,因为它的编码方式 .

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

  • 1

    听起来像@Chad K让你走上正轨( Cookies 和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#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,所以你应该能够很好地跟进 .

    看起来比原来的路线要简单得多 .

相关问题