Nonce values are committed multiple times to the
database in different phases of authentication.
The specification requires one nonce value to
protect the session from from replay attacks.
The current implementation claims nonce values
are used for CSRF protection, which is false.
This commit fixes the use of multiple tokens by
using one token to protect from replay and fixes
an issue where authentication fails with
different authorization and token endpoints.
As far as I understand the python-social-auth OpenID Connect implementation is somewhat broken at this moment.
The current implementation fails when the user has differing endpoints defined for authorization requests and token requests. A nonce value is first created in the authorization phase, then later in the token request phase. The token should only be created once per the session. The received ID Token should be decrypted and then validated against the nonce value that is created in the authorization phase, not in the later phase.
The following flow is used when calling the OpenIdConnectAuth class authentication:
auth_params is called in authorization phase, which creates a nonce value for the request and stores it in the database. This is later used to identify that we have indeed made this request.
auth_complete_params is called when authorization is succesfull; why would we be creating unique session keys at this stage?
validate_and_return_id_token is called to check if all is OK with the received token and user information. If nonce value matches in this package after decrypting with the server public key with the nonce value we created when starting the request, we trust that the request is good. If the nonce matches it is removed to remove the possibility of someone else attempting replay attack authentication in place of our user; an authentication is valid only once with a signed request.
The fix:
Remove obsolete token request nonce key; it is not used for CSRF protection and plays no role in this part.
Validate the ID Token nonce against the authorization phase nonce after decrypting the ID Token with public key to validate the ID Token is in good shape.
This is the example that brought forth the problems in the current implementation. I have a Keycloak server that I'm integrating as a SSO source:
# backends.py
from django.conf import settings
from social.backends.open_id import OpenIdConnectAuth
class KeycloakOpenIDConnect(OpenIdConnectAuth):
name = 'keycloak'
ID_TOKEN_ISSUER = settings.SOCIAL_AUTH_KEYCLOAK_URL_ISSUER
AUTHORIZATION_URL = '{0}/auth'.format(
settings.SOCIAL_AUTH_KEYCLOAK_URL_ROOT
)
ACCESS_TOKEN_URL = '{0}/token'.format(
settings.SOCIAL_AUTH_KEYCLOAK_URL_ROOT
)
ACCESS_TOKEN_METHOD = 'POST'
def get_user_details(self, response):
# Required for correct operation; represented as a stub here
return {}
As you can see, there are two different URLs for authorization and access tokens. When we create nonce requests with both and only fetch the valid requests with the settings.ACCESS_TOKEN_URL key, we are dismissing the firstly created valid nonce which should indeed be used. We are also not ever using the AUTHORIZATION_URL based nonce, which should in fact be used according the specification. It is merely created, and it is certainly not used for CSRF protection.
Nonce values are committed multiple times to the database in different phases of authentication.
The specification requires one nonce value to protect the session from from replay attacks.
The current implementation claims nonce values are used for CSRF protection, which is false.
This commit fixes the use of multiple tokens by using one token to protect from replay and fixes an issue where authentication fails with different authorization and token endpoints.
As far as I understand the
python-social-auth
OpenID Connect implementation is somewhat broken at this moment.The current implementation fails when the user has differing endpoints defined for authorization requests and token requests. A
nonce
value is first created in the authorization phase, then later in the token request phase. The token should only be created once per the session. The received ID Token should be decrypted and then validated against the nonce value that is created in the authorization phase, not in the later phase.The following flow is used when calling the
OpenIdConnectAuth
class authentication:auth_params
is called in authorization phase, which creates anonce
value for the request and stores it in the database. This is later used to identify that we have indeed made this request.auth_complete_params
is called when authorization is succesfull; why would we be creating unique session keys at this stage?validate_and_return_id_token
is called to check if all is OK with the received token and user information. Ifnonce
value matches in this package after decrypting with the server public key with thenonce
value we created when starting the request, we trust that the request is good. If thenonce
matches it is removed to remove the possibility of someone else attempting replay attack authentication in place of our user; an authentication is valid only once with a signed request.The fix:
nonce
key; it is not used for CSRF protection and plays no role in this part.nonce
against the authorization phasenonce
after decrypting the ID Token with public key to validate the ID Token is in good shape.This is the example that brought forth the problems in the current implementation. I have a Keycloak server that I'm integrating as a SSO source:
As you can see, there are two different URLs for authorization and access tokens. When we create
nonce
requests with both and only fetch the valid requests with thesettings.ACCESS_TOKEN_URL
key, we are dismissing the firstly created validnonce
which should indeed be used. We are also not ever using theAUTHORIZATION_URL
basednonce
, which should in fact be used according the specification. It is merely created, and it is certainly not used for CSRF protection.Sources: