There are four grant types defined by RFC6749, you can also create your own extended grant. Register the supported grant types to the authorization server.
Authorization Code Grant is a very common grant type, it is supported by almost every OAuth 2 providers. It uses an authorization code to exchange access token. In this case, we need a place to store the authorization code. It can be kept in a database or a cache like redis. Here is an example of database AuthorizationCode:
from django.db.models import ForeignKey, CASCADE
from django.contrib.auth.models import User
from authlib.oauth2.rfc6749 import AuthorizationCodeMixin
def now_timestamp():
return int(time.time())
class AuthorizationCode(Model, AuthorizationCodeMixin):
user = ForeignKey(User, on_delete=CASCADE)
client_id = CharField(max_length=48, db_index=True)
code = CharField(max_length=120, unique=True, null=False)
redirect_uri = TextField(default='', null=True)
response_type = TextField(default='')
scope = TextField(default='', null=True)
auth_time = IntegerField(null=False, default=now_timestamp)
def is_expired(self):
return self.auth_time + 300 < time.time()
def get_redirect_uri(self):
return self.redirect_uri
def get_scope(self):
return self.scope or ''
def get_auth_time(self):
return self.auth_time
Note here, you MUST implement the missing methods of
AuthorizationCodeMixin
API interface.
Later, you can use this AuthorizationCode
database model to handle authorization_code
grant type. Here is how:
from authlib.oauth2.rfc6749 import grants
from authlib.common.security import generate_token
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
def create_authorization_code(self, client, grant_user, request):
code = generate_token(48)
item = AuthorizationCode(
code=code,
client_id=client.client_id,
redirect_uri=request.redirect_uri,
response_type=request.response_type,
scope=request.scope,
user=grant_user,
)
item.save()
return code
def parse_authorization_code(self, code, client):
try:
item = OAuth2Code.objects.get(code=code, client_id=client.client_id)
except OAuth2Code.DoesNotExist:
return None
if not item.is_expired():
return item
def delete_authorization_code(self, authorization_code):
authorization_code.delete()
def authenticate_user(self, authorization_code):
return authorization_code.user
# register it to grant endpoint
server.register_grant(AuthorizationCodeGrant)
Note
AuthorizationCodeGrant is the most complex grant.
Default allowed Client Authentication Methods are:
You can change it in the subclass, e.g. remove the none
authentication method:
class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_basic', 'client_secret_post']
Note
This is important when you want to support OpenID Connect.
The implicit grant type is usually used in a browser, when resource owner granted the access, access token is issued in the redirect URI, there is no missing implementation, which means it can be easily registered with:
from authlib.oauth2.rfc6749 import grants
# register it to grant endpoint
server.register_grant(grants.ImplicitGrant)
Implicit Grant is used by public client which has no client_secret.
Only allowed Client Authentication Methods: none
.
Resource owner uses their username and password to exchange an access token,
this grant type should be used only when the client is trustworthy, implement
it with a subclass of ResourceOwnerPasswordCredentialsGrant
:
from authlib.oauth2.rfc6749 import grants
from django.contrib.auth.models import User
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
def authenticate_user(self, username, password):
try:
user = User.objects.get(username=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
# register it to grant endpoint
server.register_grant(PasswordGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
Client credentials grant type can access public resources and the client’s creator’s resources. It can be easily registered with:
from authlib.oauth2.rfc6749 import grants
# register it to grant endpoint
server.register_grant(grants.ClientCredentialsGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class ClientCredentialsGrant(grants.ClientCredentialsGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
Many OAuth 2 providers haven’t implemented refresh token endpoint. Authlib
provides it as a grant type, implement it with a subclass of
RefreshTokenGrant
:
from authlib.oauth2.rfc6749 import grants
class RefreshTokenGrant(grants.RefreshTokenGrant):
def authenticate_refresh_token(self, refresh_token):
try:
item = OAuth2Token.objects.get(refresh_token=refresh_token)
if item.is_refresh_token_active():
return item
except OAuth2Token.DoesNotExist:
return None
def authenticate_user(self, credential):
return credential.user
def revoke_old_credential(self, credential):
credential.revoked = True
credential.save()
# register it to grant endpoint
server.register_grant(RefreshTokenGrant)
Default allowed Client Authentication Methods: client_secret_basic
.
You can add more in the subclass:
class RefreshTokenGrant(grants.RefreshTokenGrant):
TOKEN_ENDPOINT_AUTH_METHODS = [
'client_secret_basic', 'client_secret_post'
]
By default, RefreshTokenGrant will not issue a refresh_token
in the token
response. Developers can change this behavior with:
class RefreshTokenGrant(grants.RefreshTokenGrant):
INCLUDE_NEW_REFRESH_TOKEN = True
It is also possible to create your own grant types. In Authlib, a Grant supports two endpoints:
response_type
.Creating a custom grant type with BaseGrant:
from authlib.oauth2.rfc6749.grants import (
BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin
)
class MyCustomGrant(BaseGrant, AuthorizationEndpointMixin, TokenEndpointMixin):
GRANT_TYPE = 'custom-grant-type-name'
def validate_authorization_request(self):
# only needed if using AuthorizationEndpointMixin
def create_authorization_response(self, grant_user):
# only needed if using AuthorizationEndpointMixin
def validate_token_request(self):
# only needed if using TokenEndpointMixin
def create_token_response(self):
# only needed if using TokenEndpointMixin
For a better understanding, you can read the source code of the built-in grant types. And there are extended grant types defined by other specs:
Grant can accept extensions. Developers can pass extensions when registering grant:
server.register_grant(AuthorizationCodeGrant, [extension])
For instance, there is CodeChallenge
extension in Authlib:
server.register_grant(AuthorizationCodeGrant, [CodeChallenge(required=False)])
Learn more about CodeChallenge
at RFC7636: Proof Key for Code Exchange by OAuth Public Clients.