This section contains the generic implementation of RFC8628. 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.
To integrate with Authlib Flask OAuth 2.0 Server or Django OAuth 2.0 Server, developers MUST implement the missing methods of the two classes:
There are two missing methods that developers MUST implement:
from authlib.oauth2.rfc8628 import DeviceAuthorizationEndpoint
class MyDeviceAuthorizationEndpoint(DeviceAuthorizationEndpoint):
def get_verification_uri(self):
return 'https://example.com/active'
def save_device_credential(self, client_id, scope, data):
credential = DeviceCredential(
client_id=client_id,
scope=scope,
**data
)
credential.save()
# register it to authorization server
authorization_server.register_endpoint(MyDeviceAuthorizationEndpoint)
get_verification_uri
is the URL that end user will use their browser to
log in and authenticate. See below “Verification Endpoint”.
After the registration, you can create a response with:
@app.route('/device_authorization', methods=['POST'])
def device_authorization():
return server.create_endpoint_response('device_authorization')
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, bool(allowed)
def should_slow_down(self, credential, now):
# developers can return True/False based on credential and now
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.
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']
user_code = request.form['user_code']
key = 'oauth_user_grant:' + user_code
redis.set(key, f'{current_user.id} {allowed}', 12)
return render_template('verification.html')
Check points:
get_verification_uri
in Device Authorization Endpointquery_user_grant
in Device Code Grantauthlib.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).
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.
generate_user_code
()¶A method to generate user_code
value for device authorization
endpoint. This method will generate a random string like MQNA-JPOZ.
Developers can rewrite this method to create their own user_code
.
generate_device_code
()¶A method to generate device_code
value for device authorization
endpoint. This method will generate a random string of 42 characters.
Developers can rewrite this method to create their own device_code
.
get_verification_uri
()¶Define the verification_uri
of 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()
authlib.oauth2.rfc8628.
DeviceCodeGrant
(request, 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).
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:
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
create_token_response
()¶If the access token request is valid and authorized, the authorization server issues an access token and optional refresh token.
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 |
---|
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.query.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.query.get(user_id)
return user, bool(allowed)
Note, user grant information is saved by verification endpoint.
should_slow_down
(credential, now)¶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.
save_token
(token)¶A method to save token into database.
validate_requested_scope
()¶Validate if requested scope is supported by Authorization Server.