跳转至

刷新令牌

在 Authany 中,当 OAuth 2.0 客户端应用成功用授权码换取访问令牌时,我们的授权服务器会返回一个刷新令牌。通常在 OAuth 2.0 中,访问令牌在一段时间后就会过期(您可以在 Authany 管理台中配置这个时长)。刷新令牌的作用是让您的应用能够在旧的访问令牌过期后申请新的访问令牌,从而免去用户重新走一遍完整登录流程的麻烦。

在本文中,我们将介绍如何获取刷新令牌,以及如何在旧的访问令牌过期后用它申请新的访问令牌。

前提条件

  • 本文假设您已经有一些将 Authany 用作 OIDC 提供方的经验。
  • 拥有一个 Authany 账号 和一个 Authany 应用。
  • 已记录好您 Authany 应用的配置信息(client ID、授权重定向 URI、端点等)。

如果您完全没有接触过 Authany,请先查看我们的入门指南。

如何获取刷新令牌

根据您使用的编程语言或框架不同,将 Authany 接入应用作为 Open ID Connect(OIDC)提供方的方式有很多种,这也会决定您获取刷新令牌的具体做法。例如,如果您使用的是我们的官方 SDK,其中内置的方法和配置可以简化获取刷新令牌以及续期过期访问令牌的过程。

不过,所有实现方式的共同点是:都需要在应用的授权 URL 中加入以下 scope 参数:

scope=openid+offline_access

或者仅使用 scope=offline_access 也可以。

以下是包含刷新令牌的 Authany 令牌端点响应示例:

{
  "access_token": "Rjr3abcD1234UVWXYZ1lt",
  "expires_in": 1800,
  "id_token": "eyKdsdj88sdjsdjjdfjjdfjdfjdfjkskslslslmd.eyshdhsdhnsjdksdjd7783jdjed83hd.VR848348384dd-atjdjdfjdfjsnsn-hhsdjsdjsjdjsdj-wuUb0-kb88usdusdjsjdjsdjsdsd-dddddss-kjdhdhfhdhfhhdfh3he34h",
  "refresh_token": "5385267f-24a8-4fcf-9561-0380602868e2.Z8vxeBZ6TXXOmgsc8GUALnN9puxuqcqy",
  "token_type": "Bearer"
}
  • refresh_token 字段包含刷新令牌。
  • expires_in 表示访问令牌在过期前的有效时长(单位:秒)。

以下步骤将以几种常见的语言和框架为例,演示如何从 Authany 获取刷新令牌。

第 1 步:在代码中添加 Authany 配置

在这一步,登录 Authany 管理台,进入应用页面,选择您的应用,然后按照下面的示例将应用的配置信息复制到您的代码中:

在这个 React 示例中,我们使用的是官方 Authany JavaScript SDK。使用以下命令安装:

npm install @authgear/web
import authgear from "@authgear/web";

export const endpoint = ""; // The Authgear endpoint of your project e.g. https://my-app.authanyid.com
export const clientID = ""; // Client ID can be obtained in the "Applications" page of the Portal

async function init() {
  try {
    await authgear.configure({
      endpoint,
      clientID,
      sessionType: "refresh_token"
    });
  } finally {
    createRoot(document.getElementById("react-app-root")!).render(<App />);
  }
}

// eslint-disable-next-line no-console
init().catch((e) => console.log(e));

由于 SDK 可以自动完成获取刷新令牌并用它续期访问令牌的工作,您不需要在配置中指定 offline scope。

在这个 PHP 示例中,我们使用 League OAuth 2.0 client 这个 PHP 包。使用以下命令安装该包:

composer require league/oauth2-client

然后按如下方式配置您的应用:

<?php
require 'vendor/autoload.php';
session_start(); 

$appUrl = "https://your_project.authgear.cloud";
$clientID = "";
$clientSecret = "";
$redirectUri = "http://localhost:8081/";

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    'clientId'                => $clientID,    // The client ID assigned to you by the provider
    'clientSecret'            => $clientSecret,    // The client password assigned to you by the provider
    'redirectUri'             => $redirectUri,
    'urlAuthorize'            => $appUrl . '/oauth2/authorize',
    'urlAccessToken'          => $appUrl . '/oauth2/token',
    'urlResourceOwnerDetails' => $appUrl . '/oauth2/userInfo',
    'scopes' => 'openid offline_access'
]);

请注意 'scopes' => 'openid offline_access' 这一行,offline_access 作用域是 Authany 返回刷新令牌所必需的。

在这个示例中,我们使用 Node.js 创建一个简单的 Express 应用,向 Authany 服务器发起 HTTP 请求。运行以下命令安装运行代码所需的 Node 包:

1. Express:

npm install express

2. Axios:

npm install axios

然后按如下方式在 Express 应用中配置 Authany:

const express = require('express');
const axios = require('axios');

const app = express();
const port = process.env.PORT || 3000;

const config = {
  client: {
    id: "CLIENT_ID",
    secret: "CLIENT_SECRET",
    redirect_url: "REDIRECT_URL"
  },
  auth: {
    tokenHost: "AUTHGEAR_ENDPOINT",
    tokenPath: '/oauth2/token',
    authorizePath: '/oauth2/authorize',
    scope: 'openid+offline_access'
  },
};

config.auth.scope 中的值包含了 offline_access 作用域,这是应用调用令牌端点时 Authany 返回刷新令牌所必需的。

第 2 步:从令牌端点获取刷新令牌

当您的应用向 /oauth2/token 端点发起 HTTP 请求时,只要按照第 1 步中的说明包含了 offline_access 作用域,Authany 就会连同访问令牌一起返回刷新令牌。

Note: 如果您最初发送到授权 URL 的请求中不包含 offline_access 作用域,Authany 就不会返回刷新令牌。

以下代码示例展示了如何向令牌端点发起请求,并从响应 JSON 对象中获取刷新令牌。

如前所述,Authany JavaScript SDK 可以自动完成获取刷新令牌、并在需要时用它续期访问令牌的工作。以下代码展示了如何调用该方法:

authgear
    .refreshAccessTokenIfNeeded()
    .then(() => {
        // access token is ready to use
        // accessToken can be string or undefined
        // it will be empty if user is not logged in or session is invalid
        const accessToken = authgear.accessToken;

        // include Authorization header in your application request
        const headers = {
            Authorization: `Bearer ${accessToken}`
        };
    });

使用 League OAuth client 时,您可以在 provider->getAccessToken() 返回的对象上调用 getRefreshToken() 方法,获取令牌端点返回的刷新令牌。以下代码展示了发起授权请求、用授权码换取访问令牌、并获取刷新令牌的完整流程:

if (!isset($_GET['code'])) {
    // Fetch the authorization URL from the provider; this returns the
    // urlAuthorize option and generates and applies any necessary parameters
    // (e.g. state).
    $authorizationUrl = $provider->getAuthorizationUrl();

    // Get the state generated for you and store it to the session.
    $_SESSION['oauth2state'] = $provider->getState();

    // Redirect the user to the authorization URL.
    header('Location: ' . $authorizationUrl);
    exit;
}
else {
    $code = $_GET['code'];

    if (empty($_GET['state']) || empty($_SESSION['oauth2state']) || $_GET['state'] !== $_SESSION['oauth2state']) {
        if (isset($_SESSION['oauth2state'])) {
            unset($_SESSION['oauth2state']);
        }

        exit('Invalid state');
    } else {
        try {
            $accessToken = $provider->getAccessToken('authorization_code', [
                'code' => $code
            ]);

            //Get refresh token
            $refreshToken = $accessToken->getRefreshToken();
            echo "Login successful access token:". $accessToken;
            echo " Refresh token:". $refreshToken;

        } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
            // Failed to get the access token or user details.
            exit($e->getMessage());
        }
    }
}

要在这个 Express 示例中获取刷新令牌,只需向令牌端点发送 HTTP 请求,用授权码换取访问令牌。该端点响应中的 refresh_token 字段就是刷新令牌。以下代码展示了在 Express 应用中读取 refresh_token 字段值的示例:

const data = {
  client_id: config.client.id,
  client_secret: config.client.secret,
  code: req.query.code,
  grant_type: 'authorization_code',
  response_type: 'code',
  redirect_uri: config.client.redirect_url,
  scope: config.auth.scope
};

try {
  const getToken = await axios.post(`
    ${config.auth.tokenHost}${config.auth.tokenPath}`,
    data,
    {
    headers: { "Content-Type": "application/x-www-form-urlencoded" }
    }
  );

  const accessToken = getToken.data.access_token;
  const refreshToken = getToken.data.refresh_token;
  res.send(`
  <p>${JSON.stringify(getToken.data)}</p>
    <div style="max-width: 650px; margin: 16px auto; background-color: #EDEDED; padding: 16px;">
      <p>Welcome</p>
      <p>Refresh token: ${refreshToken}</p>

    </div>
`);
} catch (error) {
  res.send("An error occurred! Login could not complete. Error data: " + error);
}

小结

要从 Authany 获取刷新令牌,您需要在授权请求中包含 offline_access。

Authany SDK 会自动处理获取 refresh token 的工作,您不需要进行任何额外配置。

建议将刷新令牌安全地存储在用户设备上,并在当前访问令牌过期时用它申请新的访问令牌。