在本文中,我们将探索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信息中心的“应用程序”>“默认应用程序”>“设置”下找到大多数设置。 请注意,我使用的是系统创建的默认应用程序。 当然,如果需要,您可以继续创建一个新的应用程序。
AUTH0_CALLBACK_URL
是您的应用程序的URL,Auth0将在登录和注销后重定向用户。 您必须在Auth0仪表板上的应用程序设置下的“ Allowed Callback URLs
下配置在此字段中设置的值。
您将找到实现大多数身份验证逻辑的三个主要文件。
- 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和环境变量相关的类。 然后,我们使用getenv
函数从.env文件初始化配置变量。
接下来,我们实例化Auth0对象并调用login方法,该方法将用户重定向到Auth0进行登录。 登录后,用户将被重定向回我们的网站。
您可以使用Facebook,Google等社交帐户登录,也可以在登录期间创建新帐户。 无论哪种情况,Auth0都会为新用户创建记录。 您可以在Auth0仪表板上的“ 连接”>“社交”下启用其他社交登录。 另外,您可以在“ 用户”链接下的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="https://i.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>
在这里,我们使用了$auth0
对象的getUser
方法来查看是否存在任何活动会话。 如果没有活动的会话,我们将显示“ 登录”链接,该链接会将用户带到login.php并启动登录流程。 另一方面,如果用户已经登录,我们将向该用户打招呼并显示“ 注销”链接。
这样就实现了服务器端应用程序的基本身份验证流程。
使用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>
请用与您的应用程序对应的值替换{AUDIENCE}
, {CLIENT_ID}
和{CALLBACK_URL}
。 {AUDIENCE}
参数应替换为Auth0仪表板上的API> {您的API应用程序}>设置下的标识符字段的值。
{SOME_RANDOM_STRING}应该替换为难以猜测的唯一值。 该字符串用于防止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}
应该替换为API> {您的API应用}>设置下的“ 签名算法”字段的值。
因此,如果您希望在Auth0服务中使用OAuth2流,这就是保护您的自定义API的方式。
结论
今天,我们经历了Auth0服务,该服务提供身份验证和授权即服务。 引入Auth0服务后,我们通过几个实际示例演示了如何将其与PHP应用程序集成。
请随时使用以下供稿发布您的建议和疑问!
翻译自: https://code.tutsplus.com/tutorials/authentication-and-authorization-using-auth0-in-php--cms-31134