OAuth Introduction

上下文中的 OAuth 仅考虑 OAuth 2.0,不考虑旧版的协议。

Protocol

OAuth 2.0 is not an authentication protocol.

As far as an OAuth client is concerned, it asked for a token, got a token, and eventually used that token to access some API. It doesn’t know anything about who authorized the application or if there was even a user there at all.

OAuth 并不是一个用户认证协议,并不关注用户是谁和或者谁在使用 API。它关注授权和维护 token。用户认证是一个更大的主题,你可以以 OAuth 来实现。

Roles

OAuth defines four roles:

  • Resource Owner: The resource owner is the user who authorizes an application to access their account. The application’s access to the user’s account is limited to the scope of the authorization granted (e.g. read or write access)
  • Client: The client is the application that wants to access the user’s account. Before it may do so, it must be authorized by the user, and the authorization must be validated by the API.
  • Resource Server: The resource server hosts the protected user accounts.
  • Authorization Server: The authorization server verifies the identity of the user then issues access tokens to the application.

Resource Owner 就是用户,用户委托 Client 以其去获取相应资源。如 Facebook / GitHub / Google 登陆是第三方网站希望获取 ID 信息,在授权确认的网页上会写有需要获取的权限,如邮箱地址。

oauth-abstrac-flow

  1. The application requests authorization to access service resources from the user
  2. If the user authorized the request, the application receives an authorization grant
  3. The application requests an access token from the authorization server (API) by presenting authentication of its own identity, and the authorization grant
  4. If the application identity is authenticated and the authorization grant is valid, the authorization server (API) issues an access token to the application. Authorization is complete.
  5. The application requests the resource from the resource server (API) and presents the access token for authentication
  6. If the access token is valid, the resource server (API) serves the resource to the application

Grant Types

在上面 Abstract Protocol Flow 图中,首先是需要获取 authorization grant 以此来获取 access token,OAuth 2 包含有以下几种类型

Authorization Code

对浏览器友好,基于 HTTP 不断地 redirect

  1. 请求 authorization server 的 API,其中包含一个注册时的 client_id 和想要获取的权限 scoperedirect_uriresponse_type=code
  2. 弹出给用户的是一个权限描述页面,用户确认后,redirect 到 client 的 redirect_uri,请求中包含一个 authorization code
  3. client 再以 code 去 authorization server 请求 token 接口获取 access token
  4. authorization server 会返回相应 access token 给到 client
    1. payload 中可能会包含一个有效期更长的 refresh token

此外,最开始 authorization code 的 request 会带有一个随机字符串 state ,客户端可以存在 cookie 中,authorization server 返回时,也会带有 state 字段以防 CSRF 攻击。

PKCE - Proof Key for Code Exchange

在 Authorization Code 流程中,authorization code 可能会被拦截,需要额外安全措施来保证 token 的生成,PKCE 就是一个该协议的扩展来帮助降低安全风险

  1. 请求 authorization server 的 API 时,Client 会新增一个随机的 code_challenge 和相应的 hash 算法 code_challenge_method 字段
  2. authorization server 记录这些字段,在 Client 请求 access token 时,其会新增一个 code_verifier 字段(使用 hash 算法转换的值)
  3. authorization server 验证该字段和之前提供的 code_challenge 转换之后的值一致,则认为该 client 合法
  4. 返回 access token

在 OKTA 服务中,提及到如果需要在浏览器端使用 PKCE,则需要浏览器支持 Web Crypto API,具体可见该 文档

Client Credentials

不是针对实际用户的,而是用于 server to server 的 API 认证

  1. 向 authorization server 发送请求,其中 grant_type=client_credentials ,还包括事前约定的 client_idclient_secret
  2. authorization server 认证之后就返回 access token

Device Code

The device code grant type provides a means for devices that lack a browser or have limited inputs to obtain an access token and access a user’s account.

为没有浏览器,或者不方便进行输入的设备而设

  1. client 向 authorization server 发送请求,endpoint 和上述两种类型不同,需要提供 client_id
  2. 返回带 device_codeuser_code
  3. 此时设备可以提供一个 QR Code,以 device_code 构成链接,用户端输入 user_code 来即可完成校验

OpenID Connect - OIDC

OpenID Connect protocol is built on the OAuth 2.0 protocol and helps authenticate users and convey information about them. It is also more opinionated than plain OAuth 2.0, for example in its scope definitions.

OAuth 本身其实不关心用户,它是一个授权协议,关注使用 token 去请求 API。OIDC 则是复用其获取 token 的流程,加以扩展功能。

OAuth 2.0 leaves a lot of details up to implementers. For instance, it supports scopes, but scope names are not specified. It supports access tokens, but the format of those tokens are not specified.

OIDC 完善了很多 OAuth 细节,包括 scope 定义,以及一个用于获取用户信息 API 等。此外一个重要的不同是,在返回 access token 时,带上了一个 id_token 。这是一个 JWT 格式的字段,解析出来就是用户信息。

OAuth 定义好了流程,OIDC 则是复用了流程定义好了如字段和格式,而且定义好了 meta 信息的 API,用以做自动发现等功能(此功能是一个 Optional 选项,详情可见 文档)。

一些常用的 API

  • /userinfo - 获取用户信息
  • /introspect - 检查 token
  • /token - 获取新的 token
  • /revoke - 取消一个 access token 或者 refresh token

Single Sign On - SSO

single-sign-on

上图是浏览器端的 SSO 流程。一个场景如下

  1. 用户登录 domain1 时,此时没有 cookie,则需要跳转到 domain3 进行登录
  2. domain3 登录成功后,则使用返回 token 和 redirect 到 domain1,domain1 使用该 token 即可完成登录
  3. 在登录成功 domain3 时,浏览存会存放有 domain3 的 cookie
  4. 当 domain2 需要登录,跳转到 domain3 则不需要进行用户认证,通过 cookie 即可确认登录状态
  5. 返回 token 和 redirect 到 domain2

这么做的原因首先是浏览器的 Same Origin Policy 限制,不同域名 cookie 不可见。所以只能通过跳转到入口 domain 来使用其 cookie。

Token

OIDC 授权结束后,client 可以拿到三个 token

  • access token
  • refresh token
  • id token

access token 用于访问资源,refresh token 则用于申请新的 access token ,id token 则是 JWT 格式封装的用户信息,除了 id token 限制使用 JWT 之外,其余没有格式要求。不同 token 的生命周期不同,以 OKTA 平台为例,其默认 token 生命周期为

  • ID token: 60 minutes
  • Access token: 60 minutes
  • Refresh token: 90 days

OKTA 中 access token 也是使用 JWT 封装,resource server 收到请求时,也可以进行时间和签名认证。其中,JWT header 包含一个 kid 字段,通过一个无需认证 endpoint /.well-known/oauth-authorization-server(这个就是上文提到的 OIDC 用于接口发现的 meta endpoint)可以查询到一个 /keys 的 URI,通过 client_idkid 即可查到相应的公钥,有了公钥就可以进行 access token 的合法性校验。

access token 到期后,需要使用 refresh token 请求 /token 接口获取新的 access token 。refresh token 生命周期更长,为了防止被滥用,可以考虑

  • 使用 Native App 需要保证 refresh token 存放到只有自己程序可以访问的区域
  • 每次申请新的 access token 之后,会带新 refresh token 下来,revoke 旧 refresh token
  • 用户退出后,revoke 旧 refresh token

在 OKTA 中,Browser-based 的 App 甚至不支持 refresh token

Note: The Authorization Code flow with PKCE doesn’t support refresh tokens for SPAs and other browser-based apps.

Conclusion

本文是对 OKTA 平台进行调研的一些笔记,我倒是重新认真看了 OAuth / SSO 的一些概念,受益颇多。个人感觉从 OAuth 进化到 OIDC 的路倒是挺有意思的。有文章介绍道说最开始是用了一个 /me 的 endpoint 来获得用户信息,但这样是有违 OAuth 设计的,于是在 OAuth 基础上实现了 OIDC。

协议的设计和使用范围的确是一个很难平衡的点,总是很容易将就使用某个协议去越级做一些事情。

References


OAuth Introduction
http://yoursite.com/2021/10/23/oauth-introduction/
Author
Shing
Posted on
October 23, 2021
Licensed under