• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

在PHP中使用Auth0进行身份验证和授权

在本文中,我们将探索 Auth0 服务,该服务将身份验证和授权作为服务提供。Auth0 允许您在眨眼之间为您的应用程序设置基本的身份验证和授权功能。

什么是 Auth0?

Auth0 是一种身份验证即服务工具,它使在您的站点中实现与身份验证相关的功能变得轻而易举。如果您已经构建了一个应用程序并且只想外包身份验证和授权功能,那么您应该考虑使用像 Auth0 这样的服务。

让我快速总结一下 Auth0 提供的功能:

  • 单点登录

  • 多因素身份验证

  • 无密码登录

  • 用户管理

  • 以及更多

在本文中,我们将介绍几种可以在 Web 应用程序中实现的单点登录方法,以利用 Auth0 服务提供的身份验证功能。

在本文的前半部分,我们将探讨如何在服务器php Web 应用程序中设置基本身份验证功能。在后半部分,我将解释如何通过使用 Auth0 服务设置 oauth 授权来保护您的自定义api

服务器端身份验证集成

在本节中,我们将介绍如何使用 Auth0 为服务器端 Web 应用程序快速设置基本身份验证。事实上,Auth0 团队已经提供了一个方便的 GitHub 示例来演示基本示例,因此我们将使用它而不是重新发明轮子。

在继续之前,请确保安装 Composer,因为它将用于使用该composer.json文件安装实际的 Auth0 SDK。此外,如果您想按照本文中的示例进行操作,请继续使用 Auth0 为自己获取一个免费帐户。

设置项目

让我们继续获取示例项目的克隆。

git clone https://github.com/auth0-samples/auth0-php-web-app.git .

继续运行composer install命令以安装依赖项。

cd 00-Starter-Seed
composer install

根据composer.json文件,它应该已经安装了 vlucas/phpdotenv和auth0/auth0-php包。

{
    "name": "auth0/basic-webapp-sample",
    "description": "Basic sample for securing a WebApp with Auth0",
    "require": {
         "vlucas/phpdotenv": "2.4.0",
         "auth0/auth0-php": "~5.0"
    },
    "license": "MIT",
    "authors": [
        {
            "name": "Martin Gontovnikas",
            "email": "martin@gon.to"
        },
        {
            "name": "Germán Lena",
            "email": "german.lena@gmail.com"
        }
    ]
}

该vlucas/phpdotenv库用于从.env文件初始化环境变量。因此,它允许您将配置与在环境之间更改的代码分开。

另一方面,auth0/auth0-php包将帮助我们在应用程序中设置授权。

接下来,让我们在.env文件中为我们的应用程序设置配置。继续并通过从.env.example文件中复制来创建.env文件。

cp .env.example .env

它包含 Auth0 库将使用的配置值。

AUTH0_CLIENT_ID={CLIENT_ID}
AUTH0_domAIN={DOMAIN_NAME}
AUTH0_CLIENT_SECRET={CLIENT_SECRET}
AUTH0_callback_URL={CALLBACK_URL}
AUTH0_AUDIENCE=

您应该能够  在 Auth0 仪表板上的Applications > Default App > Settings下找到大部分设置。请注意,我使用的是系统创建的默认应用程序。当然,如果您愿意,您可以继续创建一个新应用程序。

这AUTH0_CALLBACK_URL是您的应用程序的 URL,Auth0 将在登录和注销后重定向用户。您在此字段中设置的值必须Allowed Callback URLs在 Auth0 仪表板上的应用程序设置下进行配置。

您会发现实现大部分身份验证逻辑的三个主要文件。

  • index.php:这是根据用户状态显示登录或注销按钮的主页。

  • login.php:当你点击登录按钮时会启动这个脚本,它会将用户重定向到 Auth0 登录界面进行登录。登录后,他们将被重定向回AUTH0_CALLBACK_URL.

  • logout.php:当您单击注销按钮时,将启动此脚本,它将在后台将用户重定向到 Auth0 ,将他们注销,然后将他们返回到AUTH0_CALLBACK_URL.

重点项目文件

让我们快速浏览一下启动项目中的每个文件。

登录脚本

我们将从login.php文件开始。

<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/dotenv-loader.php';

use Auth0\SDK\Auth0;

$domain        = getenv('AUTH0_DOMAIN');
$client_id     = getenv('AUTH0_CLIENT_ID');
$client_secret = getenv('AUTH0_CLIENT_SECRET');
$redirect_uri  = getenv('AUTH0_CALLBACK_URL');
$audience      = getenv('AUTH0_AUDIENCE');

if($audience == ''){
$audience = 'https://' . $domain . '/userinfo';
}

$auth0 = new Auth0([
  'domain' => $domain,
  'client_id' => $client_id,
  'client_secret' => $client_secret,
  'redirect_uri' => $redirect_uri,
  'audience' => $audience,
  'scope' => 'openid profile',
  'persist_id_token' => true,
  'persist_access_token' => true,
  'persist_refresh_token' => true,
]);

$auth0->login();

一开始,我们已经包含了负责加载 Auth0 和环境变量相关类的自动加载器。之后,我们使用该函数从.env文件初始化配置变量。getenv

接下来,我们实例化 Auth0 对象并调用 login 方法将用户重定向到 Auth0 进行登录。登录后,用户将被重定向回我们的网站。

您可以使用 Facebook、Google 等社交帐户登录,或在登录时创建新帐户。无论哪种情况,Auth0 都会为新用户创建记录。 您可以在 Auth0 仪表板上的Connections > Social下启用不同的社交登录 。此外,您可以在用户链接下的 Auth0 仪表板上查看使用 Auth0 登录的用户列表。

注销脚本

接下来,让我们快速浏览一下logout.php文件。

<?php
require __DIR__ . '/vendor/autoload.php';
require __DIR__ . '/dotenv-loader.php';
use Auth0\SDK\Auth0;

$domain        = getenv('AUTH0_DOMAIN');
$client_id     = getenv('AUTH0_CLIENT_ID');
$client_secret = getenv('AUTH0_CLIENT_SECRET');
$redirect_uri  = getenv('AUTH0_CALLBACK_URL');
$audience      = getenv('AUTH0_AUDIENCE');

if($audience == ''){
    $audience = 'https://' . $domain . '/userinfo';
}

$auth0 = new Auth0([
  'domain' => $domain,
  'client_id' => $client_id,
  'client_secret' => $client_secret,
  'redirect_uri' => $redirect_uri,
  'audience' => $audience,
  'scope' => 'openid profile',
  'persist_id_token' => true,
  'persist_refresh_token' => true,
]);

$auth0->logout();
$return_to = 'https://' . $_SERVER['HTTP_HOST'];
$logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', $domain, $client_id, $return_to);
header('Location: ' . $logout_url);
die();

这与login.php文件的工作方式几乎相同,只是它会在用户注销时被调用。调用该logout方法以使您的应用程序中的用户会话到期。之后,用户将被重定向到 Auth0,以便通知服务有关用户的注销活动。最后,用户将被重定向回您的应用程序。

索引文件

最后,让我们浏览index.php文件,它是我们应用程序的入口点。

<?php
  // Require composer autoloader
  require __DIR__ . '/vendor/autoload.php';
  require __DIR__ . '/dotenv-loader.php';
  use Auth0\SDK\Auth0;
  $domain        = getenv('AUTH0_DOMAIN');
  $client_id     = getenv('AUTH0_CLIENT_ID');
  $client_secret = getenv('AUTH0_CLIENT_SECRET');
  $redirect_uri  = getenv('AUTH0_CALLBACK_URL');
  $audience      = getenv('AUTH0_AUDIENCE');
  if($audience == ''){
    $audience = 'https://' . $domain . '/userinfo';
  }
  $auth0 = new Auth0([
    'domain' => $domain,
    'client_id' => $client_id,
    'client_secret' => $client_secret,
    'redirect_uri' => $redirect_uri,
    'audience' => $audience,
    'scope' => 'openid profile',
    'persist_id_token' => true,
    'persist_access_token' => true,
    'persist_refresh_token' => true,
  ]);
  $userInfo = $auth0->getUser();
?>
<html>
    <head>
        <script src="http://code.jquery.com/jquery-3.1.0.min.js" type="text/javascript"></script>

        <meta name="viewport" content="width=device-width, initial-scale=1">

        <!-- font awesome from BootstrapCDN -->
        <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
        <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet">

        <link href="public/app.css" rel="stylesheet">
    </head>
    <body class="home">
        <div class="container">
            <div class="login-page clearfix">
              <?php if(!$userInfo): ?>
              <div class="login-box auth0-box before">
                <img src=" cloudup.com/StzWWrY34s.png" />
                <h3>Auth0 Example</h3>
                <p>Zero friction identity infrastructure, built for developers</p>
                <a class="btn btn-primary btn-lg btn-login btn-block" href="login.php">Sign In</a>
              </div>
              <?php else: ?>
              <div class="logged-in-box auth0-box logged-in">
                <h1 id="logo"><img src="//cdn.auth0.com/samples/auth0_logo_final_blue_RGB.png" /></h1>
                <img class="avatar" src="<?php echo $userInfo['picture'] ?>"/>
                <h2>Welcome <span class="nickname"><?php echo $userInfo['nickname'] ?></span></h2>
                <a class="btn btn-warning btn-logout" href="/logout.php">Logout</a>
              </div>
              <?php endif ?>
            </div>
        </div>
    </body>
</html>

在这里,我们使用了对象的getUser方法$auth0来查看是否有任何活动会话。如果没有活动会话,我们将显示登录链接,该链接将用户带到 login.php并启动登录流程。另一方面, 如果用户已经登录,我们将问候用户并显示注销链接。

这就是服务器端应用程序的基本身份验证流程的实现。

使用oauth2保护您的自定义 API

在本节中,我们将了解如何通过实施 OAuth2 授权代码授予流程来保护您的自定义 API。我希望您熟悉授权码授予的标准流程,因为我们不会对此进行详细介绍。

相反,我们将立即深入到实际的实现中。继续创建一个  包含以下内容的auth_code_grant_example.php文件。

<?php
session_start();

if (!isset($_GET['code'])) {
    // Check if we need to show the "Sign In" link
    $params = array (
      'audience' => '{AUDIENCE}',
      'scope' => 'profile',
      'response_type' => 'code',
      'client_id' => '{CLIENT_ID}',
      'state' => 'SomerandomString',
      'redirect_uri' => '{CALLBACK_URL}'
    );

    $_SESSION['oauth2state']=$params['state'];
    $str_params = '';
    foreach($params as $key=>$value) {
      $str_params .= $key . "=" . urlencode($value) . "&";
    }
    ?>

    <a href="https://{AUTH0_DOMAIN}/authorize?<?php echo $str_params;?>">
      Sign In
    </a>
<?php
} elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {
    // If the "state" var is present in the $_GET, let's validate it
    if (isset($_SESSION['oauth2state'])) {
        unset($_SESSION['oauth2state']);
    }
    
    exit('Invalid state');

} elseif(isset($_GET['code']) && !empty($_GET['code'])) {
    // If the auth "code" is present in the $_GET
    // let's exchange it for the access token
    $params = array (
      'grant_type' => 'authorization_code',
      'client_id' => '{CLIENT_ID}',
      'client_secret' => '{CLIENT_SECRET}',
      'code' => $_GET['code'],
      'redirect_uri' => '{CALLBACK_URL}'
    );

    $str_params = '';
    foreach($params as $key=>$value) {
      $str_params .= $key . "=" . urlencode($value) . "&";
    }

    $curl = curl_init();

    curl_setopt_array($curl, array(
      CURLOPT_URL => "https://{AUTH0_DOMAIN}/oauth/token",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_CUSTOMREQUEST => "post",
      CURLOPT_POSTFIELDS => $str_params
    ));

    $curl_response = curl_exec($curl);
    $curl_error = curl_error($curl);

    curl_close($curl);

    if ($curl_error) {
      echo "Error in the CURL response:" . $curl_error;
    } else {
      $arr_json_data = json_decode($curl_response);

      if (isset($arr_json_data->access_token)) {
        $access_token = $arr_json_data->access_token;
        $curl = curl_init();

        curl_setopt_array($curl, array(
          CURLOPT_URL => "http://{YOUR_API_DOMAIN}/demo_api_server.php",
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_CUSTOMREQUEST => "GET",
          CURLOPT_HTTPHEADER => array(
            "Authorization: Bearer {$access_token}"
          )
        ));

        $curl_response = curl_exec($curl);
        $curl_error = curl_error($curl);

        curl_close($curl);

        if ($curl_error) {
          echo "Error in the CURL response from DEMO API:" . $curl_error;
        } else {
          echo "Demo API Response:" . $curl_response;
        }
      } else {
        echo 'Invalid response, no access token was found.';
      }
    }
}

让我们看看这段代码是如何工作的!

开始授权流程

首先,我们准备了一个链接,将用户发送到 Auth0 服务器以开始授权流程。

// Check if we need to show the "Sign In" link
$params = array (
  'audience' => '{AUDIENCE}',
  'scope' => 'profile',
  'response_type' => 'code',
  'client_id' => '{CLIENT_ID}',
  'state' => '{SOME_RANDOM_STRING}',
  'redirect_uri' => '{CALLBACK_URL}'
);

$_SESSION['oauth2state']=$params['state'];
$str_params = '';
foreach($params as $key=>$value) {
  $str_params .= $key . "=" . urlencode($value) . "&";
}
?>

<a href="https://{AUTH0_DOMAIN}/authorize?<?php echo $str_params;?>">
  Sign In
</a>

请将、和替换 为与您的应用程序对应的值。该 参数应替换为在 Auth0 仪表板上的APIs > {YOUR API APPLICATION} > Settings下找到的Identifier字段的值。{AUDIENCE}{CLIENT_ID}{CALLBACK_URL}{AUDIENCE}

{SOME_RANDOM_STRING}应替换为难以猜测的唯一值。此字符串用于 pr事件 csrf攻击。此外,请确保替换{AUTH0_DOMAIN}为您的域名,正如我们之前讨论的那样。

获取访问令牌

当用户单击登录链接时,他们将被带到 Auth0 服务器进行身份验证。身份验证后,他们将被要求授权应用程序访问您的个人资料。code授权后,用户将使用参数重定向回您的应用程序$_GET。

接下来,我们可以交换它code来获取访问令牌。

// If the auth "code" is present in the $_GET
// let's exchange it for the access token
$params = array (
  'grant_type' => 'authorization_code',
  'client_id' => '{CLIENT_ID}',
  'client_secret' => '{CLIENT_SECRET}',
  'code' => $_GET['code'],
  'redirect_uri' => '{CALLBACK_URL}'
);

$str_params = '';
foreach($params as $key=>$value) {
  $str_params .= $key . "=" . urlencode($value) . "&";
}

$curl = curl_init();
$curl_response = curl_exec($curl);

curl_setopt_array($curl, array(
  CURLOPT_URL => "https://{AUTH0_DOMAIN}/oauth/token",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => "POST",
  CURLOPT_POSTFIELDS => $str_params
));

如您所见,只需一次 CURL 调用即可获取访问令牌。 

调用您的自定义 API 端点

获得访问令牌后,您可以通过将其包含在标头中来调用自定义 API 端点。

$access_token = $arr_json_data->access_token;
$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => "http://{YOUR_API_DOMAIN}/demo_api_server.php",
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_CUSTOMREQUEST => "GET",
  CURLOPT_HTTPHEADER => array(
    "Authorization: Bearer {$access_token}"
  )
));

$curl_response = curl_exec($curl);
$curl_error = curl_error($curl);

curl_close($curl);

if ($curl_error) {
  echo "Error in the CURL response from DEMO API:" . $curl_error;
} else {
  echo "Demo API Response:" . $curl_response;
}

受 Auth0 保护的 API 端点

虚构的 API 资源文件demo_api_server.php可能如下所示:

<?php
// Require composer autoloader
require __DIR__ . '/vendor/autoload.php';

use Auth0\SDK\JWTVerifier;

try {
  $verifier = new JWTVerifier([
    'supported_algs' => ['{HASHING_ALGORITHM}'],
    'valid_audiences' => ['{AUDIENCE}'],
    'authorized_iss' => ['{DOMAIN}']
  ]);

  $access_token = getBearerToken();
  $token_info = $verifier->verifyAndDecode($access_token);

  echo json_encode(array('date'=>'API Resource Data!!'));
}
catch(\Auth0\SDK\Exception\CoreException $e) {
  throw $e;
  echo json_encode(array('error'=>$e->getMessage()));
}

function getAuthorizationHeader() {
    $headers = null;

    if (isset($_SERVER['Authorization'])) {
        $headers = trim($_SERVER["Authorization"]);
    }
    else if (isset($_SERVER['HTTP_AUTHORIZATION'])) { //Nginx or fast CGI
        $headers = trim($_SERVER["HTTP_AUTHORIZATION"]);
    } elseif (function_exists('apache_request_headers')) {
        $requestHeaders = apache_request_headers();
        // Server-side fix for bug in old android versions (a nice side-effect of this fix means we don't care about capitalization for Authorization)
        $requestHeaders = array_combine(array_map('ucwords', array_keys($requestHeaders)), array_values($requestHeaders));
        //print_r($requestHeaders);
        if (isset($requestHeaders['Authorization'])) {
            $headers = trim($requestHeaders['Authorization']);
        }
    }
    return $headers;
}

function getBearerToken() {
    $headers = getAuthorizationHeader();

    // HEADER: Get the access token from the header
    if (!empty($headers)) {
        if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
            return $matches[1];
        }
    }
    return null;
}

让我们快速浏览一下这段代码的重要部分。

验证访问令牌

在授予对受保护资源的访问权限之前,您有责任验证传入的访问令牌。这正是我们在以下代码段中所做的。我们使用 JWTVerifier 实用程序类来验证访问令牌。

try {
  $verifier = new JWTVerifier([
    'supported_algs' => ['{SIGNING_ALGORITHM}'],
    'valid_audiences' => ['{AUDIENCE}'],
    'authorized_iss' => ['{DOMAIN}']
  ]);

  $access_token = getBearerToken();
  $token_info = $verifier->verifyAndDecode($access_token);

  echo json_encode(array('date'=>'API Resource Data!!'));
}
catch(\Auth0\SDK\Exception\CoreException $e) {
  throw $e;
  echo json_encode(array('error'=>$e->getMessage()));
}

{SIGNING_ALGORITHM}应替换为APIs > {YOUR API APPLICATION} > Settings下的Signing Algorithm字段 的值。

因此,如果您希望在 Auth0 服务中使用 OAuth2 流,这就是您可以保护自定义 API 的方式。

结论

今天,我们通过了 Auth0 服务,它提供了身份验证和授权作为服务。在介绍了 Auth0 服务之后,我们通过几个实际示例来演示如何将它与您的 PHP 应用程序集成。


文章目录
  • 什么是 Auth0?
  • 服务器端身份验证集成
    • 设置项目
    • 重点项目文件
      • 登录脚本
      • 注销脚本
      • 索引文件
  • 使用oauth2保护您的自定义 API
    • 开始授权流程
    • 获取访问令牌
    • 调用您的自定义 API 端点
    • 受 Auth0 保护的 API 端点
    • 验证访问令牌
  • 结论