indieauth/client

IndieAuth 客户端库

1.1.6 2022-11-08 21:37 UTC

README

这是一个简单的库,用于帮助实现 IndieAuth。您可能希望以两种方式使用它,要么在开发使用 IndieAuth 进行用户登录的应用程序时使用,要么在开发自己的用于颁发访问令牌的端点时使用,并且您需要验证授权代码。

Build Status

安装

要使用 Composer 进行安装,请运行

composer require indieauth/client

此库遵循 PSR-4 进行自动加载。要自动加载,请确保您的应用程序需要 Composer 自动加载文件

<?php

require __DIR__ . '/vendor/autoload.php';

快速入门

如果您想快速开始,并且您不介意让库直接在 PHP 会话中存储数据,那么您可以按照下面的示例进行操作。如果您需要更多的控制或想要深入了解 IndieAuth 流程的细节,请参阅下面的 客户端的详细使用说明

创建登录表单

您首先需要创建一个登录表单来提示用户输入他们的网站地址。这可能看起来像下面的 HTML。

<form action="/login.php" method="post">
  <input type="url" name="url">
  <input type="submit" value="Log In">
</form>

开始登录流程

login.php 文件中,您需要初始化会话,并告诉这个库发现用户的端点。如果一切顺利,该库将返回一个您可以使用它将用户重定向到开始流程的 URL。

下面的示例将有一些非常基本的错误处理,您可能希望用更美观的替代。

示例 login.php 文件

<?php

if(!isset($_POST['url'])) {
  die('Missing URL');
}

// Start a session for the library to be able to save state between requests.
session_start();

// You'll need to set up two pieces of information before you can use the client,
// the client ID and and the redirect URL.

// The client ID should be the home page of your app.
IndieAuth\Client::$clientID = 'https://example.com/';

// The redirect URL is where the user will be returned to after they approve the request.
IndieAuth\Client::$redirectURL = 'https://example.com/redirect.php';

// Pass the user's URL and your requested scope to the client.
// If you are writing a Micropub client, you should include at least the "create" scope.
// If you are just trying to log the user in, you can omit the second parameter.

list($authorizationURL, $error) = IndieAuth\Client::begin($_POST['url'], 'create');
// or list($authorizationURL, $error) = IndieAuth\Client::begin($_POST['url']);

// Check whether the library was able to discover the necessary endpoints
if($error) {
  echo "<p>Error: ".$error['error']."</p>";
  echo "<p>".$error['error_description']."</p>";
} else {
  // Redirect the user to their authorization endpoint
  header('Location: '.$authorizationURL);
}

以下范围对授权服务器有特殊意义,将请求用户的完整个人资料信息,而不是仅验证其个人资料 URL

  • profile
  • email

任何其他请求的范围都被假定是请求返回访问令牌的范围,库将在下一步从令牌端点请求访问令牌。

处理重定向

在您的重定向文件中,将所有查询字符串参数传递给库,它会处理一切!它将使用在初始步骤中找到的授权或令牌端点,并使用授权代码来验证个人资料信息或获取访问令牌,具体取决于您是否请求了任何范围。

结果将是授权端点或令牌的响应,其中包含用户的最终 me URL 以及如果您请求了一个或多个范围,则包含访问令牌。

如果有任何问题,错误信息也将返回给您。

库将确保最终返回的个人资料 URL 与输入的 URL 具有相同的授权端点。

示例 redirect.php 文件

<?php
session_start();
IndieAuth\Client::$clientID = 'https://example.com/';
IndieAuth\Client::$redirectURL = 'https://example.com/redirect.php';

list($response, $error) = IndieAuth\Client::complete($_GET);

if($error) {
  echo "<p>Error: ".$error['error']."</p>";
  echo "<p>".$error['error_description']."</p>";
} else {
  // Login succeeded!
  // The library will return the user's profile URL in the property "me"
  // It will also return the full response from the authorization or token endpoint, as well as debug info
  echo "URL: ".$response['me']."<br>";
  if(isset($response['response']['access_token'])) {
    echo "Access Token: ".$response['response']['access_token']."<br>";
    echo "Scope: ".$response['response']['scope']."<br>";
  }

  // The full parsed response from the endpoint will be available as:
  // $response['response']

  // The raw response:
  // $response['raw_response']

  // The HTTP response code:
  // $response['response_code']

  // You'll probably want to save the user's URL in the session
  $_SESSION['user'] = $user['me'];
}

客户端的详细使用说明

IndieAuth 客户端首先需要做的事情是提示用户输入他们的网络地址。这是 IndieAuth 的基础,其中用户标识符是 URL。一个典型的 IndieAuth 登录表单可能看起来像以下这样。

Your URL: [ example.com ]

       [ Sign In ]

此表单将向您的应用程序服务器发送 POST 请求,此时您可以开始 IndieAuth 的发现。

发现所需的端点

用户在客户端执行授权之前,需要为他们自己的URL定义端点。这些端点应通过在IndieAuth服务器元数据端点中指定,使用HTTP Link头部或带有关系indieauth-metadata<link>标签。

<link rel="indieauth-metadata" href="https://example.com/auth-metadata">

这些发现方法可以顺序执行,并且库将避免在已获取页面一次的情况下发出重复的HTTP请求。

元数据端点

首先尝试发现元数据端点。

// Normalize whatever the user entered to be a URL, e.g. "example.com" to "https://example.com/"
$url = IndieAuth\Client::normalizeMeURL($url);
$metadataEndpoint = IndieAuth\Client::discoverMetadataEndpoint($url);

如果找到,则在元数据中发现和验证issuer参数。

if ($metadataEndpoint) {
  $response = self::discoverIssuer($metadataEndpoint);
  if ($response instanceof IndieAuth\ErrorResponse) {
    // handle the error response, array with keys `error` and `error_description`
    die(json_encode($response->getArray()));
  }

  $_SESSION['indieauth_issuer'] = $response;
}

应将issuer值存储在会话中,以便在后续过程中验证授权响应(见下文)。

如果未找到元数据端点,以下方法将尝试发现用于向后兼容的个体<link>关系。

授权端点

在IndieAuth服务器元数据中

{
  "authorization_endpoint": "https://indieauth.example.com/auth"
}

或向后兼容的关系

<link rel="authorization_endpoint" href="https://example.com/auth">
Link: <https://example.com/auth>; rel="authorization_endpoint"

授权端点允许网站指定在执行初始授权请求时将用户浏览器重定向到的位置。

由于这可能是一个完整的URL,因此允许网站将其授权端点使用外部服务器。这使得人们可以将授权和认证的处理和验证委托给外部服务,以加快开发速度。当然,在任何时候,授权服务器都可以更改,并且API客户端和用户不需要进行任何修改。

以下函数将获取用户的首页并返回授权端点,或如果没有找到则返回false

// Normalize whatever the user entered to be a URL, e.g. "example.com" to "http://example.com/"
$url = IndieAuth\Client::normalizeMeURL($url);
$authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($url);

令牌端点

在IndieAuth服务器元数据中

{
  "token_endpoint": "https://indieauth.example.com/token"
}

或向后兼容的关系

<link rel="token_endpoint" href="https://example.com/token">
Link: <https://example.com/token>; rel="token_endpoint"

令牌端点是API客户端请求访问令牌的地方。这通常是在用户自己的网站上的一个URL,尽管这也可以委托给外部服务。

令牌端点负责颁发访问令牌。

以下函数将获取用户的首页并返回令牌端点,或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$tokenEndpoint = IndieAuth\Client::discoverTokenEndpoint($url);

Micropub端点

<link rel="micropub" href="https://example.com/micropub">
Link: <https://example.com/micropub>; rel="micropub"

Micropub端点定义了Micropub客户端将向用户的网站创建新帖子发送POST请求的地方。当Micropub客户端发出请求时,请求将包含在标题中之前颁发的访问令牌,并且micropub端点将能够根据该访问令牌验证请求。

以下函数将获取用户的首页并返回Micropub端点,或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$micropubEndpoint = IndieAuth\Client::discoverMicropubEndpoint($url);

客户端可能希望在开始时发现所有端点,并将值缓存到会话中以供以后使用。

Microsub端点

<link rel="microsub" href="https://example.com/microsub">
Link: <https://example.com/microsub>; rel="microsub"

Microsub端点是阅读器使用的。当Micropub客户端发出请求时,请求将包含在标题中之前颁发的访问令牌,并且端点将能够根据该访问令牌验证请求。

以下函数将获取用户的首页并返回Microsub端点,或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$microsubEndpoint = IndieAuth\Client::discoverMicrosubEndpoint($url);

客户端可能希望在开始时发现所有端点,并将值缓存到会话中以供以后使用。

其他端点

IndieAuth服务器元数据允许定义一些其他端点

令牌撤销

返回revocation_endpoint或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$revocationEndpoint = IndieAuth\Client::discoverRevocationEndpoint($url);

令牌洞察

返回introspection_endpoint或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$introspectionEndpoint = IndieAuth\Client::discoverIntrospectionEndpoint($url);

用户信息

返回userinfo_endpoint或如果没有找到则返回false

$url = IndieAuth\Client::normalizeMeURL($url);
$userinfoEndpoint = IndieAuth\Client::discoverUserinfoEndpoint($url);

构建授权URL

一旦客户端发现了授权服务器,它将需要构建授权URL并将用户浏览器重定向到那里。

对于网站,客户端应将301重定向发送到授权URL,或可以打开新的浏览器窗口。原生应用必须启动原生浏览器窗口到授权URL,并适当地处理重定向回原生应用。

授权端点参数

  • me - 用户开始流程时输入的URL。
  • redirect_uri - 授权服务器授权完成后应重定向的位置。
  • client_id - 应用程序网页的完整URL。授权服务器使用它来发现应用的名称和图标,并验证重定向URI。
  • state - "state"参数可以是客户端希望的任何内容,并且还必须在客户端用授权码交换访问令牌时发送到令牌端点。
  • scope - "scope"值是客户端请求的权限的空格分隔列表。
  • code_challenge - 对于PKCE支持,这是客户端开始时生成的秘密的哈希版本。
  • code_challenge_method - 此库始终使用S256作为哈希方法。

以下函数将根据所有必需的参数构建授权URL。如果授权端点包含查询字符串,此函数将处理将现有查询字符串参数与新参数合并。

以下范围对授权服务器有特殊意义,将请求用户的完整个人资料信息,而不是仅验证其个人资料 URL

  • profile
  • email
$url = IndieAuth\Client::normalizeMeURL($url);

$scope = 'profile create'; // Request profile info as well as an access token with the "create" scope

// These are two random strings. The helper methods in the library will use an available random number generaton depending on the PHP version.
$_SESSION['state'] = IndieAuth\Client::generateStateParameter();
$_SESSION['code_verifier'] = IndieAuth\Client::generatePKCECodeVerifier();

// you'll need to verify these later
$_SESSION['user_entered_url'] = $url;
$_SESSION['authorization_endpoint'] = $authorizationEndpoint;

$authorizationURL = IndieAuth\Client::buildAuthorizationURL($authorizationEndpoint, [
  'me' => $url,
  'redirect_uri' => $redirect_uri,
  'client_id' => $client_id,
  'scope' => $scope,
  'state' => $_SESSION['state'],
  'code_verifier' => $_SESSION['code_verifier'],
]);

注意:您的代码应包括明文随机代码验证器,IndieAuth\Client库将为您在请求中处理哈希。

获取用户的授权

在此阶段,授权服务器与用户交互,向他们展示请求的描述。这看起来类似于以下典型的OAuth提示

An application, "Quill" is requesting access to your website, "aaronparecki.com"

This application would like to be able to
* **create** new entries on your website

[ Approve ]   [ Deny ]

如果用户批准请求,授权服务器将重定向回指定的重定向URL,并在查询字符串中添加以下参数

  • code - 授权码
  • state - 请求中提供的state值
  • iss - 客户端验证的发行者标识符

验证授权响应

接下来,必须验证stateiss参数以匹配授权请求

$response = self::validateStateMatch($_GET, $_SESSION['indieauth_state']);
if ($response instanceof IndieAuth\ErrorResponse) {
  // handle the error response, array with keys `error` and `error_description`
  die(json_encode($response->getArray()));
}

if (isset($_SESSION['indieauth_issuer'])) {
  $response = self::validateIssuerMatch($_GET, $_SESSION['indieauth_issuer']);
  if ($response instanceof IndieAuth\ErrorResponse) {
    // handle the error response, array with keys `error` and `error_description`
    die(json_encode($response->getArray()));
  }
}

如果两者都有效,则可以继续交换授权码。

交换授权码以获取配置文件信息

如果客户端不是试图获取访问令牌,而是仅尝试验证用户的URL,则它需要在授权端点交换授权码以获取配置文件信息。

以下函数将对授权端点发起POST请求并解析结果。

$response = IndieAuth\Client::exchangeAuthorizationCode($authorizationEndpoint, [
  'code' => $_GET['code'],
  'redirect_uri' => $redirect_uri,
  'client_id' => $client_id,
  'code_verifier' => $_SESSION['code_verifier'],
]);

$response变量将包含端点的响应,如下所示

array(
  'me' => 'https://aaronparecki.com/',
  'response' => [
    'me' => 'https://aaronparecki.com/',
    'profile' => [
      'name' => 'Aaron Parecki',
      'url' => 'https://aaronparecki.com/',
      'photo' => 'https://aaronparecki.com/images/profile.jpg'
    ]
  ],
  'raw_response' => '{"me":"https://aaronparecki.com/","profile":{"name":"Aaron Parecki","url":"https://aaronparecki.com/","photo":"https://aaronparecki.com/images/profile.jpg"}}',
  'response_code' => 200
);

交换授权码以获取访问令牌

如果客户端请求了超出配置文件范围的任何作用域并期望访问令牌,则它需要在令牌端点交换授权码以获取访问令牌。

要获取访问令牌,客户端需要向令牌端点发起POST请求,传递授权码以及以下参数

  • code - 获得的授权码
  • me - 用户的URL
  • redirect_uri - 必须与请求授权码时使用的重定向URI匹配
  • client_id - 必须与初始请求中使用的客户端ID匹配
  • code_verifier - 如果客户端在授权请求中包含了一个code challenge,那么它必须在代码交换步骤中包含明文秘密

以下函数将向令牌端点发起POST请求并解析结果。

$response = IndieAuth\Client::exchangeAuthorizationCode($tokenEndpoint, [
  'code' => $_GET['code'],
  'redirect_uri' => $redirect_uri,
  'client_id' => $client_id,
  'code_verifier' => $_SESSION['code_verifier'],
]);

$response变量将包含令牌端点的响应,如下所示

array(
  'response' => [
    'me' => 'https://aaronparecki.com/',
    'access_token' => 'xxxxxxxxx',
    'scope' => 'create'
  ],
  'raw_response' => '{"me":"https://aaronparecki.com/","access_token":"xxxxxxxxx","scope":"create"}',
  'response_code' => 200
);

验证授权服务器

如果您使用的是单独的方法而不是begin/complete包装器,那么您需要检查返回的URL是否具有与您用于开始流程的相同的授权端点。

if($response['me'] != $_SESSION['user_entered_url']) {
  $authorizationEndpoint = IndieAuth\Client::discoverAuthorizationEndpoint($response['me']);
  if($authorizationEndpoint != $_SESSION['authorization_endpoint']) {
    die("The authorization endpoint at the profile URL is not the same as the one used to begin the flow!");
  }
}

发起API请求

在这个阶段,您已经完成了IndieAuth客户端库的使用,可以开始直接向用户的网站和micropub端点发送API请求。

要发送API请求,请将访问令牌包含在以下HTTP "Authorization"头部中:

Authorization: Bearer xxxxxxxx

附加设置

如果需要,您可以更新以下一些设置

HTTP用户代理

默认的用户代理UA模仿浏览器。您可以通过调用以下方式来自定义它:

IndieAuth\Client::$http->set_user_agent('Your User Agent String');

状态参数中的字节数

默认状态参数使用8个随机字节生成。您可以通过调用以下方式更改字节数:

IndieAuth\Client::$random_byte_count = 16;

许可证

版权所有2013-2022,Aaron Parecki及贡献者

在MIT和Apache 2.0许可证下提供。请参阅LICENSE.txt