Use stored user consent for public clients too (#189)

When using Implicit Flow, it should be OK to use the stored user consent
even if the client is public.  The redirect uri checks should make sure
that the stored consent of another client cannot be misused to get a
consent to a site that is not related to the client.

It is also important to support this, since public clients using
Implicit Flow do not have a refresh token to update their access tokens,
so only way to keep their login session open is by issuing authorization
requests from an iframe with the "prompt=none" parameter (which does not
work without the previously stored consent).  See the following links
for more info and examples on how to renew the access token with SPAs:

https://auth0.com/docs/api-auth/tutorials/silent-authentication#refresh-expired-tokens

https://damienbod.com/2017/06/02/

https://github.com/IdentityServer/IdentityServer3/issues/719#issuecomment-230145034
This commit is contained in:
Tuomas Suutari 2017-07-07 14:18:36 +03:00 committed by Wojciech Bartosiak
parent 1215c27d7e
commit 5165312d01
2 changed files with 42 additions and 9 deletions

View file

@ -314,7 +314,7 @@ class AuthorizationCodeFlowTestCase(TestCase, AuthorizeEndpointMixin):
def test_public_client_auto_approval(self):
"""
It's recommended not auto-approving requests for non-confidential clients.
It's recommended not auto-approving requests for non-confidential clients using Authorization Code.
"""
data = {
'client_id': self.client_public_with_no_consent.client_id,
@ -449,6 +449,9 @@ class AuthorizationImplicitFlowTestCase(TestCase, AuthorizeEndpointMixin):
self.user = create_fake_user()
self.client = create_fake_client(response_type='id_token token')
self.client_public = create_fake_client(response_type='id_token token', is_public=True)
self.client_public_no_consent = create_fake_client(
response_type='id_token token', is_public=True,
require_consent=False)
self.client_no_access = create_fake_client(response_type='id_token')
self.client_public_no_access = create_fake_client(response_type='id_token', is_public=True)
self.state = uuid.uuid4().hex
@ -582,6 +585,28 @@ class AuthorizationImplicitFlowTestCase(TestCase, AuthorizeEndpointMixin):
self.assertNotIn('at_hash', id_token)
def test_public_client_implicit_auto_approval(self):
"""
Public clients using Implicit Flow should be able to reuse consent.
"""
data = {
'client_id': self.client_public_no_consent.client_id,
'response_type': self.client_public_no_consent.response_type,
'redirect_uri': self.client_public_no_consent.default_redirect_uri,
'scope': 'openid email',
'state': self.state,
'nonce': self.nonce,
}
response = self._auth_request('get', data, is_user_authenticated=True)
response_text = response.content.decode('utf-8')
self.assertEquals(response_text, '')
components = urlsplit(response['Location'])
fragment = parse_qs(components[4])
self.assertIn('access_token', fragment)
self.assertIn('id_token', fragment)
self.assertIn('expires_in', fragment)
class AuthorizationHybridFlowTestCase(TestCase, AuthorizeEndpointMixin):
"""

View file

@ -66,7 +66,7 @@ class AuthorizeView(View):
client=authorize.client)
if hook_resp:
return hook_resp
if 'login' in authorize.params['prompt']:
if 'none' in authorize.params['prompt']:
raise AuthorizeError(authorize.params['redirect_uri'], 'login_required', authorize.grant_type)
@ -85,14 +85,22 @@ class AuthorizeView(View):
if {'none', 'consent'}.issubset(authorize.params['prompt']):
raise AuthorizeError(authorize.params['redirect_uri'], 'consent_required', authorize.grant_type)
if 'consent' not in authorize.params['prompt']:
if not authorize.client.require_consent and not (authorize.client.client_type == 'public'):
return redirect(authorize.create_response_uri())
implicit_flow_resp_types = set(['id_token', 'id_token token'])
allow_skipping_consent = (
authorize.client.client_type != 'public' or
authorize.client.response_type in implicit_flow_resp_types)
if authorize.client.reuse_consent:
# Check if user previously give consent.
if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public'):
return redirect(authorize.create_response_uri())
if not authorize.client.require_consent and (
allow_skipping_consent and
'consent' not in authorize.params['prompt']):
return redirect(authorize.create_response_uri())
if authorize.client.reuse_consent:
# Check if user previously give consent.
if authorize.client_has_user_consent() and (
allow_skipping_consent and
'consent' not in authorize.params['prompt']):
return redirect(authorize.create_response_uri())
if 'none' in authorize.params['prompt']:
raise AuthorizeError(authorize.params['redirect_uri'], 'consent_required', authorize.grant_type)