RFC8628: OAuth 2.0 Device Authorization Grant¶
This section contains the generic implementation of RFC 8628. OAuth 2.0 Device Authorization Grant is usually used when devices have limited input capabilities or lack a suitable browser, such as smart TVs, media consoles, picture frames, printers and etc.
The device and the user act in parallel. The device polls the token endpoint, while the user approves the request in a browser on another device:
sequenceDiagram
autonumber
participant Device as Client (device)
participant User as User's browser<br/>(User Agent)
participant AS as Authorization Server
participant RS as Resource Server
Device->>AS: POST /device_authorization
AS-->>Device: 200 OK + device_code, user_code
Note over Device: Shows user_code<br/>and verification_uri
par Device polls
loop Every interval seconds
Device->>AS: POST /token (device_code)
AS-->>Device: 400 authorization_pending
end
and User approves
User->>AS: GET /active
AS-->>User: 200 OK + form
Note over User,AS: The user logs in<br/>and enters user_code
User->>AS: POST /active (user_code, allowed)
AS-->>User: 200 OK
end
Device->>AS: POST /token (device_code)
AS-->>Device: 200 OK + access token
Device->>RS: GET /api (bearer token)
RS-->>Device: 200 OK + resource
The device requests a device code.
DeviceAuthorizationEndpointhandles this request.The response contains
device_code,user_code,verification_uri,verification_uri_complete,expires_inandinterval. The device showsuser_codeandverification_urito the user.The device sends
grant_type=urn:ietf:params:oauth:grant-type:device_codeand thedevice_code.DeviceCodeGranthandles this request.While the user has not answered, the response is
authorization_pending. It isslow_downif the device polls too often,expired_tokenif the code has expired, andaccess_deniedif the user refused.The user opens
verification_uriin a browser. The application implements this endpoint.The user logs in. The verification endpoint shows a form to enter
user_code.The user submits
user_codeand approves or denies the request.The verification endpoint saves the user decision.
The device polls again.
The user has approved: the response contains the access token.
The device calls the API of the Resource Server with the access token, in the
Authorization: Bearerheader.The Resource Server returns the protected resource.
The /device_authorization, /active, /token and /api paths are
examples.
To integrate with Authlib Flask Integration or Django Integration, developers MUST implement the missing methods of the two classes:
Device Code Grant¶
With Authlib .register_grant, we can add DeviceCodeGrant easily.
But first, we need to implement the missing methods:
from authlib.oauth2.rfc8628 import DeviceCodeGrant
class MyDeviceCodeGrant(DeviceCodeGrant):
def query_device_credential(self, device_code):
return DeviceCredential.query(device_code=device_code)
def query_user_grant(self, user_code):
data = redis.get('oauth_user_grant:' + user_code)
if not data:
return None
user_id, allowed = data.split()
user = User.query.get(user_id)
return user, allowed == '1'
def should_slow_down(self, credential):
# developers can return True/False based on credential
return False
authorization_server.register_grant(MyDeviceCodeGrant)
Note query_user_grant, we are fetching data from redis. This data
was saved from verification endpoint when end user granted the request.
Verification Endpoint¶
Developers MUST implement this part by themselves. Here is a hint on how to implement this endpoint:
@app.route('/active', methods=['GET', 'POST'])
@login_required
def verify_device_code():
if request.method == 'GET':
return render_template('verification.html')
allowed = request.form['allowed'] == 'yes'
user_code = request.form['user_code']
key = 'oauth_user_grant:' + user_code
redis.set(key, f'{current_user.id} {int(allowed)}', 12)
return render_template('verification.html')
Check points:
route should match
get_verification_uriin Device Authorization Endpointuser grant should match
query_user_grantin Device Code Grant
API Reference¶
- class authlib.oauth2.rfc8628.DeviceAuthorizationEndpoint(server)¶
This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:
+----------+ +----------------+ | |>---(A)-- Client Identifier --->| | | | | | | |<---(B)-- Device Code, ---<| | | | User Code, | | | Device | & Verification URI | | | Client | | | | | [polling] | | | |>---(E)-- Device Code --->| | | | & Client Identifier | | | | | Authorization | | |<---(F)-- Access Token ---<| Server | +----------+ (& Optional Refresh Token) | | v | | : | | (C) User Code & Verification URI | | : | | v | | +----------+ | | | End User | | | | at |<---(D)-- End user reviews --->| | | Browser | authorization request | | +----------+ +----------------+
This DeviceAuthorizationEndpoint is the implementation of step (A) and (B).
The client requests access from the authorization server and includes its client identifier in the request.
The authorization server issues a device code and an end-user code and provides the end-user verification URI.
- USER_CODE_TYPE = 'string'¶
customize “user_code” type, string or digital
- EXPIRES_IN = 1800¶
The lifetime in seconds of the “device_code” and “user_code”
- INTERVAL = 5¶
The minimum amount of time in seconds that the client SHOULD wait between polling requests to the token endpoint.
- authenticate_client(request)¶
client_id is REQUIRED if the client is not authenticating with the authorization server as described in Section 3.2.1. of [RFC6749].
This means the endpoint support “none” authentication method. In this case, this endpoint’s auth methods are:
client_secret_basic
client_secret_post
none
Developers change the value of
CLIENT_AUTH_METHODSin subclass. For instance:class MyDeviceAuthorizationEndpoint(DeviceAuthorizationEndpoint): # only support ``client_secret_basic`` auth method CLIENT_AUTH_METHODS = ["client_secret_basic"]
- generate_user_code()¶
A method to generate
user_codevalue for device authorization endpoint. This method will generate a random string like MQNA-JPOZ. Developers can rewrite this method to create their ownuser_code.
- generate_device_code()¶
A method to generate
device_codevalue for device authorization endpoint. This method will generate a random string of 42 characters. Developers can rewrite this method to create their owndevice_code.
- get_verification_uri()¶
Define the
verification_uriof device authorization endpoint. Developers MUST implement this method in subclass:def get_verification_uri(self): return "https://your-company.com/active"
- save_device_credential(client_id, scope, data)¶
Save device token into database for later use. Developers MUST implement this method in subclass:
def save_device_credential(self, client_id, scope, data): item = DeviceCredential(client_id=client_id, scope=scope, **data) item.save()
- class authlib.oauth2.rfc8628.DeviceCodeGrant(request: OAuth2Request, server)¶
This OAuth 2.0 [RFC6749] protocol extension enables OAuth clients to request user authorization from applications on devices that have limited input capabilities or lack a suitable browser. Such devices include smart TVs, media consoles, picture frames, and printers, which lack an easy input method or a suitable browser required for traditional OAuth interactions. Here is the authorization flow:
+----------+ +----------------+ | |>---(A)-- Client Identifier --->| | | | | | | |<---(B)-- Device Code, ---<| | | | User Code, | | | Device | & Verification URI | | | Client | | | | | [polling] | | | |>---(E)-- Device Code --->| | | | & Client Identifier | | | | | Authorization | | |<---(F)-- Access Token ---<| Server | +----------+ (& Optional Refresh Token) | | v | | : | | (C) User Code & Verification URI | | : | | v | | +----------+ | | | End User | | | | at |<---(D)-- End user reviews --->| | | Browser | authorization request | | +----------+ +----------------+
This DeviceCodeGrant is the implementation of step (E) and (F).
While the end user reviews the client’s request (step D), the client repeatedly polls the authorization server to find out if the user completed the user authorization step. The client includes the device code and its client identifier.
The authorization server validates the device code provided by the client and responds with the access token if the client is granted access, an error if they are denied access, or an indication that the client should continue to poll.
- GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'¶
Designed for which “grant_type”
- TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post', 'none']¶
Allowed client auth methods for token endpoint
- validate_token_request()¶
After displaying instructions to the user, the client creates an access token request and sends it to the token endpoint with the following parameters:
- grant_type
REQUIRED. Value MUST be set to “urn:ietf:params:oauth:grant-type:device_code”.
- device_code
REQUIRED. The device verification code, “device_code” from the device authorization response.
- client_id
REQUIRED if the client is not authenticating with the authorization server as described in Section 3.2.1. of [RFC6749]. The client identifier as described in Section 2.2 of [RFC6749].
For example, the client makes the following HTTPS request:
POST /token HTTP/1.1 Host: server.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS &client_id=1406020730
- query_device_credential(device_code)¶
Get device credential from previously savings via
DeviceAuthorizationEndpoint. Developers MUST implement it in subclass:def query_device_credential(self, device_code): return DeviceCredential.get(device_code)
- Parameters:
device_code – a string represent the code.
- Returns:
DeviceCredential instance
- query_user_grant(user_code)¶
Get user and grant via the given user code. Developers MUST implement it in subclass:
def query_user_grant(self, user_code): # e.g. we saved user grant info in redis data = redis.get("oauth_user_grant:" + user_code) if not data: return None user_id, allowed = data.split() user = User.get(user_id) return user, allowed == "1"
Note, user grant information is saved by verification endpoint.
- should_slow_down(credential)¶
The authorization request is still pending and polling should continue, but the interval MUST be increased by 5 seconds for this and all subsequent requests.
- TOKEN_ENDPOINT_HTTP_METHODS = ['POST']¶
Allowed HTTP methods of this token endpoint
- authenticate_token_endpoint_client()¶
Authenticate client with the given methods for token endpoint.
For example, the client makes the following HTTP request using TLS:
POST /token HTTP/1.1 Host: server.example.com Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW Content-Type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
Default available methods are: “none”, “client_secret_basic” and “client_secret_post”.
- Returns:
client
- save_token(token)¶
A method to save token into database.
- validate_requested_scope()¶
Validate if requested scope is supported by Authorization Server.