We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
prompt=none not carried for silent authentication
The [documentation](https://auth0.com/docs/api-auth/tutorials/silent-authentication) is more complex that it needs to be. 

To authenticate you visit `/v1/openid/auth0/login?callback=http...&scopes=...` it redirects to the OIDC provider (e.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...`) which then redirects you back to the `callback` (if all goes well with the password check) with a code that kinto trades in for an access token which it then appends to the `callback` URL.

Now, if that access token expires (or is *about to* expire!) we need to authenticate again. The default expiration time for the access token (at least with Auth0) is 1 day. To do that, you can simply add `&prompt=none` to the URL. E.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...&prompt=none`. What the OIDC provider does is...

> "Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page."

It's basically the same URL as authorization but with `&prompt=none` added. 
prompt=none not carried for silent authentication
The [documentation](https://auth0.com/docs/api-auth/tutorials/silent-authentication) is more complex that it needs to be. 

To authenticate you visit `/v1/openid/auth0/login?callback=http...&scopes=...` it redirects to the OIDC provider (e.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...`) which then redirects you back to the `callback` (if all goes well with the password check) with a code that kinto trades in for an access token which it then appends to the `callback` URL.

Now, if that access token expires (or is *about to* expire!) we need to authenticate again. The default expiration time for the access token (at least with Auth0) is 1 day. To do that, you can simply add `&prompt=none` to the URL. E.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...&prompt=none`. What the OIDC provider does is...

> "Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page."

It's basically the same URL as authorization but with `&prompt=none` added. 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kinto/plugins/openid/views.py`
Content:
```
1 import urllib.parse
2 
3 import colander
4 import requests
5 from pyramid import httpexceptions
6 
7 from cornice.validators import colander_validator
8 from kinto.core import Service
9 from kinto.core.errors import raise_invalid, ERRORS
10 from kinto.core.utils import random_bytes_hex
11 from kinto.core.resource.schema import ErrorResponseSchema
12 from kinto.core.schema import URL
13 
14 from .utils import fetch_openid_config
15 
16 
17 DEFAULT_STATE_TTL_SECONDS = 3600
18 DEFAULT_STATE_LENGTH = 32
19 
20 
21 class RedirectHeadersSchema(colander.MappingSchema):
22     """Redirect response headers."""
23     location = colander.SchemaNode(colander.String(), name='Location')
24 
25 
26 class RedirectResponseSchema(colander.MappingSchema):
27     """Redirect response schema."""
28     headers = RedirectHeadersSchema()
29 
30 
31 response_schemas = {
32     '307': RedirectResponseSchema(description='Successful redirection.'),
33     '400': ErrorResponseSchema(description='The request is invalid.'),
34 }
35 
36 
37 def provider_validator(request, **kwargs):
38     """
39     This validator verifies that the validator in URL (eg. /openid/auth0/login)
40     is a configured OpenIDConnect policy.
41     """
42     provider = request.matchdict['provider']
43     used = request.registry.settings.get('multiauth.policy.%s.use' % provider, '')
44     if not used.endswith('OpenIDConnectPolicy'):
45         request.errors.add('path', 'provider', 'Unknow provider %r' % provider)
46 
47 
48 class LoginQuerystringSchema(colander.MappingSchema):
49     """
50     Querystring schema for the login endpoint.
51     """
52     callback = URL()
53     scope = colander.SchemaNode(colander.String())
54 
55 
56 class LoginSchema(colander.MappingSchema):
57     querystring = LoginQuerystringSchema()
58 
59 
60 login = Service(name='openid_login',
61                 path='/openid/{provider}/login',
62                 description='Initiate the OAuth2 login')
63 
64 
65 @login.get(schema=LoginSchema(),
66            validators=(colander_validator, provider_validator),
67            response_schemas=response_schemas)
68 def get_login(request):
69     """Initiates to login dance for the specified scopes and callback URI
70     using appropriate redirections."""
71 
72     # Settings.
73     provider = request.matchdict['provider']
74     settings_prefix = 'multiauth.policy.%s.' % provider
75     issuer = request.registry.settings[settings_prefix + 'issuer']
76     client_id = request.registry.settings[settings_prefix + 'client_id']
77     userid_field = request.registry.settings.get(settings_prefix + 'userid_field')
78     state_ttl = int(request.registry.settings.get(settings_prefix + 'state_ttl_seconds',
79                                                   DEFAULT_STATE_TTL_SECONDS))
80     state_length = int(request.registry.settings.get(settings_prefix + 'state_length',
81                                                      DEFAULT_STATE_LENGTH))
82 
83     # Read OpenID configuration (cached by issuer)
84     oid_config = fetch_openid_config(issuer)
85     auth_endpoint = oid_config['authorization_endpoint']
86 
87     scope = request.GET['scope']
88     callback = request.GET['callback']
89 
90     # Check that email scope is requested if userid field is configured as email.
91     if userid_field == 'email' and 'email' not in scope:
92         error_details = {
93             'name': 'scope',
94             'description': "Provider %s requires 'email' scope" % provider,
95         }
96         raise_invalid(request, **error_details)
97 
98     # Generate a random string as state.
99     # And save it until code is traded.
100     state = random_bytes_hex(state_length)
101     request.registry.cache.set('openid:state:' + state, callback, ttl=state_ttl)
102 
103     # Redirect the client to the Identity Provider that will eventually redirect
104     # to the OpenID token endpoint.
105     token_uri = request.route_url('openid_token', provider=provider) + '?'
106     params = dict(client_id=client_id, response_type='code', scope=scope,
107                   redirect_uri=token_uri, state=state)
108     redirect = '{}?{}'.format(auth_endpoint, urllib.parse.urlencode(params))
109     raise httpexceptions.HTTPTemporaryRedirect(redirect)
110 
111 
112 class TokenQuerystringSchema(colander.MappingSchema):
113     """
114     Querystring schema for the token endpoint.
115     """
116     code = colander.SchemaNode(colander.String())
117     state = colander.SchemaNode(colander.String())
118 
119 
120 class TokenSchema(colander.MappingSchema):
121     querystring = TokenQuerystringSchema()
122 
123 
124 token = Service(name='openid_token',
125                 path='/openid/{provider}/token',
126                 description='')
127 
128 
129 @token.get(schema=TokenSchema(),
130            validators=(colander_validator, provider_validator))
131 def get_token(request):
132     """Trades the specified code and state against access and ID tokens.
133     The client is redirected to the original ``callback`` URI with the
134     result in querystring."""
135 
136     # Settings.
137     provider = request.matchdict['provider']
138     settings_prefix = 'multiauth.policy.%s.' % provider
139     issuer = request.registry.settings[settings_prefix + 'issuer']
140     client_id = request.registry.settings[settings_prefix + 'client_id']
141     client_secret = request.registry.settings[settings_prefix + 'client_secret']
142 
143     # Read OpenID configuration (cached by issuer)
144     oid_config = fetch_openid_config(issuer)
145     token_endpoint = oid_config['token_endpoint']
146 
147     code = request.GET['code']
148     state = request.GET['state']
149 
150     # State can be used only once.
151     callback = request.registry.cache.delete('openid:state:' + state)
152     if callback is None:
153         error_details = {
154             'name': 'state',
155             'description': 'Invalid state',
156             'errno': ERRORS.INVALID_AUTH_TOKEN.value,
157         }
158         raise_invalid(request, **error_details)
159 
160     # Trade the code for tokens on the Identity Provider.
161     # Google Identity requires to specify again redirect_uri.
162     redirect_uri = request.route_url('openid_token', provider=provider) + '?'
163     data = {
164         'code': code,
165         'client_id': client_id,
166         'client_secret': client_secret,
167         'redirect_uri': redirect_uri,
168         'grant_type': 'authorization_code',
169     }
170     resp = requests.post(token_endpoint, data=data)
171 
172     # The IdP response is forwarded to the client in the querystring/location hash.
173     # (eg. callback=`http://localhost:3000/#tokens=`)
174     redirect = callback + urllib.parse.quote(resp.text)
175     raise httpexceptions.HTTPTemporaryRedirect(redirect)
176
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kinto/plugins/openid/views.py b/kinto/plugins/openid/views.py
--- a/kinto/plugins/openid/views.py
+++ b/kinto/plugins/openid/views.py
@@ -51,6 +51,9 @@
     """
     callback = URL()
     scope = colander.SchemaNode(colander.String())
+    prompt = colander.SchemaNode(colander.String(),
+                                 validator=colander.Regex("none"),
+                                 missing=colander.drop)
 
 
 class LoginSchema(colander.MappingSchema):
@@ -86,6 +89,7 @@
 
     scope = request.GET['scope']
     callback = request.GET['callback']
+    prompt = request.GET.get('prompt')
 
     # Check that email scope is requested if userid field is configured as email.
     if userid_field == 'email' and 'email' not in scope:
@@ -105,6 +109,9 @@
     token_uri = request.route_url('openid_token', provider=provider) + '?'
     params = dict(client_id=client_id, response_type='code', scope=scope,
                   redirect_uri=token_uri, state=state)
+    if prompt:
+        # The 'prompt' parameter is optional.
+        params['prompt'] = prompt
     redirect = '{}?{}'.format(auth_endpoint, urllib.parse.urlencode(params))
     raise httpexceptions.HTTPTemporaryRedirect(redirect)

{"golden_diff": "diff --git a/kinto/plugins/openid/views.py b/kinto/plugins/openid/views.py\n--- a/kinto/plugins/openid/views.py\n+++ b/kinto/plugins/openid/views.py\n@@ -51,6 +51,9 @@\n     \"\"\"\n     callback = URL()\n     scope = colander.SchemaNode(colander.String())\n+    prompt = colander.SchemaNode(colander.String(),\n+                                 validator=colander.Regex(\"none\"),\n+                                 missing=colander.drop)\n \n \n class LoginSchema(colander.MappingSchema):\n@@ -86,6 +89,7 @@\n \n     scope = request.GET['scope']\n     callback = request.GET['callback']\n+    prompt = request.GET.get('prompt')\n \n     # Check that email scope is requested if userid field is configured as email.\n     if userid_field == 'email' and 'email' not in scope:\n@@ -105,6 +109,9 @@\n     token_uri = request.route_url('openid_token', provider=provider) + '?'\n     params = dict(client_id=client_id, response_type='code', scope=scope,\n                   redirect_uri=token_uri, state=state)\n+    if prompt:\n+        # The 'prompt' parameter is optional.\n+        params['prompt'] = prompt\n     redirect = '{}?{}'.format(auth_endpoint, urllib.parse.urlencode(params))\n     raise httpexceptions.HTTPTemporaryRedirect(redirect)\n", "issue": "prompt=none not carried for silent authentication\nThe [documentation](https://auth0.com/docs/api-auth/tutorials/silent-authentication) is more complex that it needs to be. \r\n\r\nTo authenticate you visit `/v1/openid/auth0/login?callback=http...&scopes=...` it redirects to the OIDC provider (e.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...`) which then redirects you back to the `callback` (if all goes well with the password check) with a code that kinto trades in for an access token which it then appends to the `callback` URL.\r\n\r\nNow, if that access token expires (or is *about to* expire!) we need to authenticate again. The default expiration time for the access token (at least with Auth0) is 1 day. To do that, you can simply add `&prompt=none` to the URL. E.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...&prompt=none`. What the OIDC provider does is...\r\n\r\n> \"Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page.\"\r\n\r\nIt's basically the same URL as authorization but with `&prompt=none` added. \nprompt=none not carried for silent authentication\nThe [documentation](https://auth0.com/docs/api-auth/tutorials/silent-authentication) is more complex that it needs to be. \r\n\r\nTo authenticate you visit `/v1/openid/auth0/login?callback=http...&scopes=...` it redirects to the OIDC provider (e.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...`) which then redirects you back to the `callback` (if all goes well with the password check) with a code that kinto trades in for an access token which it then appends to the `callback` URL.\r\n\r\nNow, if that access token expires (or is *about to* expire!) we need to authenticate again. The default expiration time for the access token (at least with Auth0) is 1 day. To do that, you can simply add `&prompt=none` to the URL. E.g. `https://peterbecom.auth0.com/authorize?client_id=...&response_type=code&scope=openid+email+profile&redirect_uri=...&state=...&prompt=none`. What the OIDC provider does is...\r\n\r\n> \"Silent authentication lets you perform an authentication flow where Auth0 will only reply with redirects, and never with a login page.\"\r\n\r\nIt's basically the same URL as authorization but with `&prompt=none` added. \n", "before_files": [{"content": "import urllib.parse\n\nimport colander\nimport requests\nfrom pyramid import httpexceptions\n\nfrom cornice.validators import colander_validator\nfrom kinto.core import Service\nfrom kinto.core.errors import raise_invalid, ERRORS\nfrom kinto.core.utils import random_bytes_hex\nfrom kinto.core.resource.schema import ErrorResponseSchema\nfrom kinto.core.schema import URL\n\nfrom .utils import fetch_openid_config\n\n\nDEFAULT_STATE_TTL_SECONDS = 3600\nDEFAULT_STATE_LENGTH = 32\n\n\nclass RedirectHeadersSchema(colander.MappingSchema):\n    \"\"\"Redirect response headers.\"\"\"\n    location = colander.SchemaNode(colander.String(), name='Location')\n\n\nclass RedirectResponseSchema(colander.MappingSchema):\n    \"\"\"Redirect response schema.\"\"\"\n    headers = RedirectHeadersSchema()\n\n\nresponse_schemas = {\n    '307': RedirectResponseSchema(description='Successful redirection.'),\n    '400': ErrorResponseSchema(description='The request is invalid.'),\n}\n\n\ndef provider_validator(request, **kwargs):\n    \"\"\"\n    This validator verifies that the validator in URL (eg. /openid/auth0/login)\n    is a configured OpenIDConnect policy.\n    \"\"\"\n    provider = request.matchdict['provider']\n    used = request.registry.settings.get('multiauth.policy.%s.use' % provider, '')\n    if not used.endswith('OpenIDConnectPolicy'):\n        request.errors.add('path', 'provider', 'Unknow provider %r' % provider)\n\n\nclass LoginQuerystringSchema(colander.MappingSchema):\n    \"\"\"\n    Querystring schema for the login endpoint.\n    \"\"\"\n    callback = URL()\n    scope = colander.SchemaNode(colander.String())\n\n\nclass LoginSchema(colander.MappingSchema):\n    querystring = LoginQuerystringSchema()\n\n\nlogin = Service(name='openid_login',\n                path='/openid/{provider}/login',\n                description='Initiate the OAuth2 login')\n\n\n@login.get(schema=LoginSchema(),\n           validators=(colander_validator, provider_validator),\n           response_schemas=response_schemas)\ndef get_login(request):\n    \"\"\"Initiates to login dance for the specified scopes and callback URI\n    using appropriate redirections.\"\"\"\n\n    # Settings.\n    provider = request.matchdict['provider']\n    settings_prefix = 'multiauth.policy.%s.' % provider\n    issuer = request.registry.settings[settings_prefix + 'issuer']\n    client_id = request.registry.settings[settings_prefix + 'client_id']\n    userid_field = request.registry.settings.get(settings_prefix + 'userid_field')\n    state_ttl = int(request.registry.settings.get(settings_prefix + 'state_ttl_seconds',\n                                                  DEFAULT_STATE_TTL_SECONDS))\n    state_length = int(request.registry.settings.get(settings_prefix + 'state_length',\n                                                     DEFAULT_STATE_LENGTH))\n\n    # Read OpenID configuration (cached by issuer)\n    oid_config = fetch_openid_config(issuer)\n    auth_endpoint = oid_config['authorization_endpoint']\n\n    scope = request.GET['scope']\n    callback = request.GET['callback']\n\n    # Check that email scope is requested if userid field is configured as email.\n    if userid_field == 'email' and 'email' not in scope:\n        error_details = {\n            'name': 'scope',\n            'description': \"Provider %s requires 'email' scope\" % provider,\n        }\n        raise_invalid(request, **error_details)\n\n    # Generate a random string as state.\n    # And save it until code is traded.\n    state = random_bytes_hex(state_length)\n    request.registry.cache.set('openid:state:' + state, callback, ttl=state_ttl)\n\n    # Redirect the client to the Identity Provider that will eventually redirect\n    # to the OpenID token endpoint.\n    token_uri = request.route_url('openid_token', provider=provider) + '?'\n    params = dict(client_id=client_id, response_type='code', scope=scope,\n                  redirect_uri=token_uri, state=state)\n    redirect = '{}?{}'.format(auth_endpoint, urllib.parse.urlencode(params))\n    raise httpexceptions.HTTPTemporaryRedirect(redirect)\n\n\nclass TokenQuerystringSchema(colander.MappingSchema):\n    \"\"\"\n    Querystring schema for the token endpoint.\n    \"\"\"\n    code = colander.SchemaNode(colander.String())\n    state = colander.SchemaNode(colander.String())\n\n\nclass TokenSchema(colander.MappingSchema):\n    querystring = TokenQuerystringSchema()\n\n\ntoken = Service(name='openid_token',\n                path='/openid/{provider}/token',\n                description='')\n\n\n@token.get(schema=TokenSchema(),\n           validators=(colander_validator, provider_validator))\ndef get_token(request):\n    \"\"\"Trades the specified code and state against access and ID tokens.\n    The client is redirected to the original ``callback`` URI with the\n    result in querystring.\"\"\"\n\n    # Settings.\n    provider = request.matchdict['provider']\n    settings_prefix = 'multiauth.policy.%s.' % provider\n    issuer = request.registry.settings[settings_prefix + 'issuer']\n    client_id = request.registry.settings[settings_prefix + 'client_id']\n    client_secret = request.registry.settings[settings_prefix + 'client_secret']\n\n    # Read OpenID configuration (cached by issuer)\n    oid_config = fetch_openid_config(issuer)\n    token_endpoint = oid_config['token_endpoint']\n\n    code = request.GET['code']\n    state = request.GET['state']\n\n    # State can be used only once.\n    callback = request.registry.cache.delete('openid:state:' + state)\n    if callback is None:\n        error_details = {\n            'name': 'state',\n            'description': 'Invalid state',\n            'errno': ERRORS.INVALID_AUTH_TOKEN.value,\n        }\n        raise_invalid(request, **error_details)\n\n    # Trade the code for tokens on the Identity Provider.\n    # Google Identity requires to specify again redirect_uri.\n    redirect_uri = request.route_url('openid_token', provider=provider) + '?'\n    data = {\n        'code': code,\n        'client_id': client_id,\n        'client_secret': client_secret,\n        'redirect_uri': redirect_uri,\n        'grant_type': 'authorization_code',\n    }\n    resp = requests.post(token_endpoint, data=data)\n\n    # The IdP response is forwarded to the client in the querystring/location hash.\n    # (eg. callback=`http://localhost:3000/#tokens=`)\n    redirect = callback + urllib.parse.quote(resp.text)\n    raise httpexceptions.HTTPTemporaryRedirect(redirect)\n", "path": "kinto/plugins/openid/views.py"}], "after_files": [{"content": "import urllib.parse\n\nimport colander\nimport requests\nfrom pyramid import httpexceptions\n\nfrom cornice.validators import colander_validator\nfrom kinto.core import Service\nfrom kinto.core.errors import raise_invalid, ERRORS\nfrom kinto.core.utils import random_bytes_hex\nfrom kinto.core.resource.schema import ErrorResponseSchema\nfrom kinto.core.schema import URL\n\nfrom .utils import fetch_openid_config\n\n\nDEFAULT_STATE_TTL_SECONDS = 3600\nDEFAULT_STATE_LENGTH = 32\n\n\nclass RedirectHeadersSchema(colander.MappingSchema):\n    \"\"\"Redirect response headers.\"\"\"\n    location = colander.SchemaNode(colander.String(), name='Location')\n\n\nclass RedirectResponseSchema(colander.MappingSchema):\n    \"\"\"Redirect response schema.\"\"\"\n    headers = RedirectHeadersSchema()\n\n\nresponse_schemas = {\n    '307': RedirectResponseSchema(description='Successful redirection.'),\n    '400': ErrorResponseSchema(description='The request is invalid.'),\n}\n\n\ndef provider_validator(request, **kwargs):\n    \"\"\"\n    This validator verifies that the validator in URL (eg. /openid/auth0/login)\n    is a configured OpenIDConnect policy.\n    \"\"\"\n    provider = request.matchdict['provider']\n    used = request.registry.settings.get('multiauth.policy.%s.use' % provider, '')\n    if not used.endswith('OpenIDConnectPolicy'):\n        request.errors.add('path', 'provider', 'Unknow provider %r' % provider)\n\n\nclass LoginQuerystringSchema(colander.MappingSchema):\n    \"\"\"\n    Querystring schema for the login endpoint.\n    \"\"\"\n    callback = URL()\n    scope = colander.SchemaNode(colander.String())\n    prompt = colander.SchemaNode(colander.String(),\n                                 validator=colander.Regex(\"none\"),\n                                 missing=colander.drop)\n\n\nclass LoginSchema(colander.MappingSchema):\n    querystring = LoginQuerystringSchema()\n\n\nlogin = Service(name='openid_login',\n                path='/openid/{provider}/login',\n                description='Initiate the OAuth2 login')\n\n\n@login.get(schema=LoginSchema(),\n           validators=(colander_validator, provider_validator),\n           response_schemas=response_schemas)\ndef get_login(request):\n    \"\"\"Initiates to login dance for the specified scopes and callback URI\n    using appropriate redirections.\"\"\"\n\n    # Settings.\n    provider = request.matchdict['provider']\n    settings_prefix = 'multiauth.policy.%s.' % provider\n    issuer = request.registry.settings[settings_prefix + 'issuer']\n    client_id = request.registry.settings[settings_prefix + 'client_id']\n    userid_field = request.registry.settings.get(settings_prefix + 'userid_field')\n    state_ttl = int(request.registry.settings.get(settings_prefix + 'state_ttl_seconds',\n                                                  DEFAULT_STATE_TTL_SECONDS))\n    state_length = int(request.registry.settings.get(settings_prefix + 'state_length',\n                                                     DEFAULT_STATE_LENGTH))\n\n    # Read OpenID configuration (cached by issuer)\n    oid_config = fetch_openid_config(issuer)\n    auth_endpoint = oid_config['authorization_endpoint']\n\n    scope = request.GET['scope']\n    callback = request.GET['callback']\n    prompt = request.GET.get('prompt')\n\n    # Check that email scope is requested if userid field is configured as email.\n    if userid_field == 'email' and 'email' not in scope:\n        error_details = {\n            'name': 'scope',\n            'description': \"Provider %s requires 'email' scope\" % provider,\n        }\n        raise_invalid(request, **error_details)\n\n    # Generate a random string as state.\n    # And save it until code is traded.\n    state = random_bytes_hex(state_length)\n    request.registry.cache.set('openid:state:' + state, callback, ttl=state_ttl)\n\n    # Redirect the client to the Identity Provider that will eventually redirect\n    # to the OpenID token endpoint.\n    token_uri = request.route_url('openid_token', provider=provider) + '?'\n    params = dict(client_id=client_id, response_type='code', scope=scope,\n                  redirect_uri=token_uri, state=state)\n    if prompt:\n        # The 'prompt' parameter is optional.\n        params['prompt'] = prompt\n    redirect = '{}?{}'.format(auth_endpoint, urllib.parse.urlencode(params))\n    raise httpexceptions.HTTPTemporaryRedirect(redirect)\n\n\nclass TokenQuerystringSchema(colander.MappingSchema):\n    \"\"\"\n    Querystring schema for the token endpoint.\n    \"\"\"\n    code = colander.SchemaNode(colander.String())\n    state = colander.SchemaNode(colander.String())\n\n\nclass TokenSchema(colander.MappingSchema):\n    querystring = TokenQuerystringSchema()\n\n\ntoken = Service(name='openid_token',\n                path='/openid/{provider}/token',\n                description='')\n\n\n@token.get(schema=TokenSchema(),\n           validators=(colander_validator, provider_validator))\ndef get_token(request):\n    \"\"\"Trades the specified code and state against access and ID tokens.\n    The client is redirected to the original ``callback`` URI with the\n    result in querystring.\"\"\"\n\n    # Settings.\n    provider = request.matchdict['provider']\n    settings_prefix = 'multiauth.policy.%s.' % provider\n    issuer = request.registry.settings[settings_prefix + 'issuer']\n    client_id = request.registry.settings[settings_prefix + 'client_id']\n    client_secret = request.registry.settings[settings_prefix + 'client_secret']\n\n    # Read OpenID configuration (cached by issuer)\n    oid_config = fetch_openid_config(issuer)\n    token_endpoint = oid_config['token_endpoint']\n\n    code = request.GET['code']\n    state = request.GET['state']\n\n    # State can be used only once.\n    callback = request.registry.cache.delete('openid:state:' + state)\n    if callback is None:\n        error_details = {\n            'name': 'state',\n            'description': 'Invalid state',\n            'errno': ERRORS.INVALID_AUTH_TOKEN.value,\n        }\n        raise_invalid(request, **error_details)\n\n    # Trade the code for tokens on the Identity Provider.\n    # Google Identity requires to specify again redirect_uri.\n    redirect_uri = request.route_url('openid_token', provider=provider) + '?'\n    data = {\n        'code': code,\n        'client_id': client_id,\n        'client_secret': client_secret,\n        'redirect_uri': redirect_uri,\n        'grant_type': 'authorization_code',\n    }\n    resp = requests.post(token_endpoint, data=data)\n\n    # The IdP response is forwarded to the client in the querystring/location hash.\n    # (eg. callback=`http://localhost:3000/#tokens=`)\n    redirect = callback + urllib.parse.quote(resp.text)\n    raise httpexceptions.HTTPTemporaryRedirect(redirect)\n", "path": "kinto/plugins/openid/views.py"}]}