We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Time skew between login.gov and cloud.gov ('issued in the future') causing a 401 on login
### Current Behavior

User gets a 401 error screen on login occasionally (about 60% of the time). Issue seems more prevalent on sandboxes and in fresh browsers with clear cache.

User is usually able to bypass this error screen by clicking login again or clicking on the .gov site logo (sometimes it takes more than one attempt).

### Expected Behavior

Succefull login and no error screen.

### Steps to Reproduce

1. Log into a sandbox in a fresh browser or incognito window.
2. Try a couple of times if the error does not trigger at first.


### Environment

_No response_

### Additional Context

_No response_

### Issue Links

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/djangooidc/oidc.py`
Content:
```
1 # coding: utf-8
2 from __future__ import unicode_literals
3 
4 import logging
5 import json
6 
7 from django.conf import settings
8 from django.http import HttpResponseRedirect
9 from Cryptodome.PublicKey.RSA import importKey
10 from jwkest.jwk import RSAKey  # type: ignore
11 from oic import oic, rndstr, utils
12 from oic.oauth2 import ErrorResponse
13 from oic.oic import AuthorizationRequest, AuthorizationResponse, RegistrationResponse
14 from oic.oic.message import AccessTokenResponse
15 from oic.utils.authn.client import CLIENT_AUTHN_METHOD
16 from oic.utils import keyio
17 
18 from . import exceptions as o_e
19 
20 __author__ = "roland"
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class Client(oic.Client):
26     def __init__(self, op):
27         """Step 1: Configure the OpenID Connect client."""
28         logger.debug("Initializing the OpenID Connect client...")
29         try:
30             provider = settings.OIDC_PROVIDERS[op]
31             verify_ssl = getattr(settings, "OIDC_VERIFY_SSL", True)
32         except Exception as err:
33             logger.error(err)
34             logger.error("Configuration missing for OpenID Connect client")
35             raise o_e.InternalError()
36 
37         try:
38             # prepare private key for authentication method of private_key_jwt
39             key_bundle = keyio.KeyBundle()
40             rsa_key = importKey(provider["client_registration"]["sp_private_key"])
41             key = RSAKey(key=rsa_key, use="sig")
42             key_bundle.append(key)
43             keyjar = keyio.KeyJar(verify_ssl=verify_ssl)
44             keyjar.add_kb("", key_bundle)
45         except Exception as err:
46             logger.error(err)
47             logger.error(
48                 "Key jar preparation failed for %s",
49                 provider["srv_discovery_url"],
50             )
51             raise o_e.InternalError()
52 
53         try:
54             # create the oic client instance
55             super().__init__(
56                 client_id=None,
57                 client_authn_method=CLIENT_AUTHN_METHOD,
58                 keyjar=keyjar,
59                 settings=utils.settings.OicClientSettings(verify_ssl=verify_ssl),
60                 config=None,
61             )
62             # must be set after client is initialized
63             self.behaviour = provider["behaviour"]
64         except Exception as err:
65             logger.error(err)
66             logger.error(
67                 "Client creation failed for %s",
68                 provider["srv_discovery_url"],
69             )
70             raise o_e.InternalError()
71 
72         try:
73             # discover and store the provider (OP) urls, etc
74             self.provider_config(provider["srv_discovery_url"])
75             self.store_registration_info(
76                 RegistrationResponse(**provider["client_registration"])
77             )
78         except Exception as err:
79             logger.error(err)
80             logger.error(
81                 "Provider info discovery failed for %s",
82                 provider["srv_discovery_url"],
83             )
84             raise o_e.InternalError()
85 
86     def create_authn_request(
87         self,
88         session,
89         extra_args=None,
90     ):
91         """Step 2: Construct a login URL at OP's domain and send the user to it."""
92         logger.debug("Creating the OpenID Connect authn request...")
93         state = rndstr(size=32)
94         try:
95             session["state"] = state
96             session["nonce"] = rndstr(size=32)
97             scopes = list(self.behaviour.get("scope", []))
98             scopes.append("openid")
99             request_args = {
100                 "response_type": self.behaviour.get("response_type"),
101                 "scope": " ".join(set(scopes)),
102                 "state": session["state"],
103                 "nonce": session["nonce"],
104                 "redirect_uri": self.registration_response["redirect_uris"][0],
105                 "acr_values": self.behaviour.get("acr_value"),
106             }
107 
108             if extra_args is not None:
109                 request_args.update(extra_args)
110         except Exception as err:
111             logger.error(err)
112             logger.error("Failed to assemble request arguments for %s" % state)
113             raise o_e.InternalError(locator=state)
114 
115         logger.debug("request args: %s" % request_args)
116 
117         try:
118             # prepare the request for sending
119             cis = self.construct_AuthorizationRequest(request_args=request_args)
120             logger.debug("request: %s" % cis)
121 
122             # obtain the url and headers from the prepared request
123             url, body, headers, cis = self.uri_and_body(
124                 AuthorizationRequest,
125                 cis,
126                 method="GET",
127                 request_args=request_args,
128             )
129             logger.debug("body: %s" % body)
130             logger.debug("URL: %s" % url)
131             logger.debug("headers: %s" % headers)
132         except Exception as err:
133             logger.error(err)
134             logger.error("Failed to prepare request for %s" % state)
135             raise o_e.InternalError(locator=state)
136 
137         try:
138             # create the redirect object
139             response = HttpResponseRedirect(str(url))
140             # add headers to the object, if any
141             if headers:
142                 for key, value in headers.items():
143                     response[key] = value
144         except Exception as err:
145             logger.error(err)
146             logger.error("Failed to create redirect object for %s" % state)
147             raise o_e.InternalError(locator=state)
148 
149         return response
150 
151     def callback(self, unparsed_response, session):
152         """Step 3: Receive OP's response, request an access token, and user info."""
153         logger.debug("Processing the OpenID Connect callback response...")
154         state = session.get("state", "")
155         try:
156             # parse the response from OP
157             authn_response = self.parse_response(
158                 AuthorizationResponse,
159                 unparsed_response,
160                 sformat="dict",
161                 keyjar=self.keyjar,
162             )
163         except Exception as err:
164             logger.error(err)
165             logger.error("Unable to parse response for %s" % state)
166             raise o_e.AuthenticationFailed(locator=state)
167 
168         # ErrorResponse is not raised, it is passed back...
169         if isinstance(authn_response, ErrorResponse):
170             error = authn_response.get("error", "")
171             if error == "login_required":
172                 logger.warning(
173                     "User was not logged in (%s), trying again for %s" % (error, state)
174                 )
175                 return self.create_authn_request(session)
176             else:
177                 logger.error("Unable to process response %s for %s" % (error, state))
178                 raise o_e.AuthenticationFailed(locator=state)
179 
180         logger.debug("authn_response %s" % authn_response)
181 
182         if not authn_response.get("state", None):
183             logger.error("State value not received from OP for %s" % state)
184             raise o_e.AuthenticationFailed(locator=state)
185 
186         if authn_response["state"] != session.get("state", None):
187             # this most likely means the user's Django session vanished
188             logger.error("Received state not the same as expected for %s" % state)
189             raise o_e.AuthenticationFailed(locator=state)
190 
191         if self.behaviour.get("response_type") == "code":
192             # need an access token to get user info (and to log the user out later)
193             self._request_token(
194                 authn_response["state"], authn_response["code"], session
195             )
196 
197         user_info = self._get_user_info(state, session)
198 
199         return user_info
200 
201     def _get_user_info(self, state, session):
202         """Get information from OP about the user."""
203         scopes = list(self.behaviour.get("user_info_request", []))
204         scopes.append("openid")
205         try:
206             # get info about the user from OP
207             info_response = self.do_user_info_request(
208                 state=session["state"],
209                 method="GET",
210                 scope=" ".join(set(scopes)),
211             )
212         except Exception as err:
213             logger.error(err)
214             logger.error("Unable to request user info for %s" % state)
215             raise o_e.AuthenticationFailed(locator=state)
216 
217         # ErrorResponse is not raised, it is passed back...
218         if isinstance(info_response, ErrorResponse):
219             logger.error(
220                 "Unable to get user info (%s) for %s"
221                 % (info_response.get("error", ""), state)
222             )
223             raise o_e.AuthenticationFailed(locator=state)
224 
225         logger.debug("user info: %s" % info_response)
226         return info_response.to_dict()
227 
228     def _request_token(self, state, code, session):
229         """Request a token from OP to allow us to then request user info."""
230         try:
231             token_response = self.do_access_token_request(
232                 scope="openid",
233                 state=state,
234                 request_args={
235                     "code": code,
236                     "redirect_uri": self.registration_response["redirect_uris"][0],
237                     "client_id": self.client_id,
238                     "client_secret": self.client_secret,
239                 },
240                 authn_method=self.registration_response["token_endpoint_auth_method"],
241             )
242         except Exception as err:
243             logger.error(err)
244             logger.error("Unable to obtain access token for %s" % state)
245             raise o_e.AuthenticationFailed(locator=state)
246 
247         # ErrorResponse is not raised, it is passed back...
248         if isinstance(token_response, ErrorResponse):
249             logger.error(
250                 "Unable to get token (%s) for %s"
251                 % (token_response.get("error", ""), state)
252             )
253             raise o_e.AuthenticationFailed(locator=state)
254 
255         logger.debug("token response %s" % token_response)
256 
257         try:
258             # get the token and other bits of info
259             id_token = token_response["id_token"]._dict
260 
261             if id_token["nonce"] != session["nonce"]:
262                 logger.error("Received nonce not the same as expected for %s" % state)
263                 raise o_e.AuthenticationFailed(locator=state)
264 
265             session["id_token"] = id_token
266             session["id_token_raw"] = getattr(self, "id_token_raw", None)
267             session["access_token"] = token_response["access_token"]
268             session["refresh_token"] = token_response.get("refresh_token", "")
269             session["expires_in"] = token_response.get("expires_in", "")
270             self.id_token[state] = getattr(self, "id_token_raw", None)
271         except Exception as err:
272             logger.error(err)
273             logger.error("Unable to parse access token response for %s" % state)
274             raise o_e.AuthenticationFailed(locator=state)
275 
276     def store_response(self, resp, info):
277         """Make raw ID token available for internal use."""
278         if isinstance(resp, AccessTokenResponse):
279             info = json.loads(info)
280             self.id_token_raw = info["id_token"]
281 
282         super(Client, self).store_response(resp, info)
283 
284     def __repr__(self):
285         return "Client {} {} {}".format(
286             self.client_id,
287             self.client_prefs,
288             self.behaviour,
289         )
290
```
--- 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/src/djangooidc/oidc.py b/src/djangooidc/oidc.py
--- a/src/djangooidc/oidc.py
+++ b/src/djangooidc/oidc.py
@@ -238,6 +238,9 @@
                     "client_secret": self.client_secret,
                 },
                 authn_method=self.registration_response["token_endpoint_auth_method"],
+                # There is a time desync issue between login.gov and cloud
+                # this addresses that by adding a clock skew.
+                skew=10,
             )
         except Exception as err:
             logger.error(err)

{"golden_diff": "diff --git a/src/djangooidc/oidc.py b/src/djangooidc/oidc.py\n--- a/src/djangooidc/oidc.py\n+++ b/src/djangooidc/oidc.py\n@@ -238,6 +238,9 @@\n                     \"client_secret\": self.client_secret,\n                 },\n                 authn_method=self.registration_response[\"token_endpoint_auth_method\"],\n+                # There is a time desync issue between login.gov and cloud\n+                # this addresses that by adding a clock skew.\n+                skew=10,\n             )\n         except Exception as err:\n             logger.error(err)\n", "issue": "Time skew between login.gov and cloud.gov ('issued in the future') causing a 401 on login\n### Current Behavior\n\nUser gets a 401 error screen on login occasionally (about 60% of the time). Issue seems more prevalent on sandboxes and in fresh browsers with clear cache.\r\n\r\nUser is usually able to bypass this error screen by clicking login again or clicking on the .gov site logo (sometimes it takes more than one attempt).\n\n### Expected Behavior\n\nSuccefull login and no error screen.\n\n### Steps to Reproduce\n\n1. Log into a sandbox in a fresh browser or incognito window.\r\n2. Try a couple of times if the error does not trigger at first.\r\n\n\n### Environment\n\n_No response_\n\n### Additional Context\n\n_No response_\n\n### Issue Links\n\n_No response_\n", "before_files": [{"content": "# coding: utf-8\nfrom __future__ import unicode_literals\n\nimport logging\nimport json\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect\nfrom Cryptodome.PublicKey.RSA import importKey\nfrom jwkest.jwk import RSAKey  # type: ignore\nfrom oic import oic, rndstr, utils\nfrom oic.oauth2 import ErrorResponse\nfrom oic.oic import AuthorizationRequest, AuthorizationResponse, RegistrationResponse\nfrom oic.oic.message import AccessTokenResponse\nfrom oic.utils.authn.client import CLIENT_AUTHN_METHOD\nfrom oic.utils import keyio\n\nfrom . import exceptions as o_e\n\n__author__ = \"roland\"\n\nlogger = logging.getLogger(__name__)\n\n\nclass Client(oic.Client):\n    def __init__(self, op):\n        \"\"\"Step 1: Configure the OpenID Connect client.\"\"\"\n        logger.debug(\"Initializing the OpenID Connect client...\")\n        try:\n            provider = settings.OIDC_PROVIDERS[op]\n            verify_ssl = getattr(settings, \"OIDC_VERIFY_SSL\", True)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Configuration missing for OpenID Connect client\")\n            raise o_e.InternalError()\n\n        try:\n            # prepare private key for authentication method of private_key_jwt\n            key_bundle = keyio.KeyBundle()\n            rsa_key = importKey(provider[\"client_registration\"][\"sp_private_key\"])\n            key = RSAKey(key=rsa_key, use=\"sig\")\n            key_bundle.append(key)\n            keyjar = keyio.KeyJar(verify_ssl=verify_ssl)\n            keyjar.add_kb(\"\", key_bundle)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Key jar preparation failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n        try:\n            # create the oic client instance\n            super().__init__(\n                client_id=None,\n                client_authn_method=CLIENT_AUTHN_METHOD,\n                keyjar=keyjar,\n                settings=utils.settings.OicClientSettings(verify_ssl=verify_ssl),\n                config=None,\n            )\n            # must be set after client is initialized\n            self.behaviour = provider[\"behaviour\"]\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Client creation failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n        try:\n            # discover and store the provider (OP) urls, etc\n            self.provider_config(provider[\"srv_discovery_url\"])\n            self.store_registration_info(\n                RegistrationResponse(**provider[\"client_registration\"])\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Provider info discovery failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n    def create_authn_request(\n        self,\n        session,\n        extra_args=None,\n    ):\n        \"\"\"Step 2: Construct a login URL at OP's domain and send the user to it.\"\"\"\n        logger.debug(\"Creating the OpenID Connect authn request...\")\n        state = rndstr(size=32)\n        try:\n            session[\"state\"] = state\n            session[\"nonce\"] = rndstr(size=32)\n            scopes = list(self.behaviour.get(\"scope\", []))\n            scopes.append(\"openid\")\n            request_args = {\n                \"response_type\": self.behaviour.get(\"response_type\"),\n                \"scope\": \" \".join(set(scopes)),\n                \"state\": session[\"state\"],\n                \"nonce\": session[\"nonce\"],\n                \"redirect_uri\": self.registration_response[\"redirect_uris\"][0],\n                \"acr_values\": self.behaviour.get(\"acr_value\"),\n            }\n\n            if extra_args is not None:\n                request_args.update(extra_args)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to assemble request arguments for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        logger.debug(\"request args: %s\" % request_args)\n\n        try:\n            # prepare the request for sending\n            cis = self.construct_AuthorizationRequest(request_args=request_args)\n            logger.debug(\"request: %s\" % cis)\n\n            # obtain the url and headers from the prepared request\n            url, body, headers, cis = self.uri_and_body(\n                AuthorizationRequest,\n                cis,\n                method=\"GET\",\n                request_args=request_args,\n            )\n            logger.debug(\"body: %s\" % body)\n            logger.debug(\"URL: %s\" % url)\n            logger.debug(\"headers: %s\" % headers)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to prepare request for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        try:\n            # create the redirect object\n            response = HttpResponseRedirect(str(url))\n            # add headers to the object, if any\n            if headers:\n                for key, value in headers.items():\n                    response[key] = value\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to create redirect object for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        return response\n\n    def callback(self, unparsed_response, session):\n        \"\"\"Step 3: Receive OP's response, request an access token, and user info.\"\"\"\n        logger.debug(\"Processing the OpenID Connect callback response...\")\n        state = session.get(\"state\", \"\")\n        try:\n            # parse the response from OP\n            authn_response = self.parse_response(\n                AuthorizationResponse,\n                unparsed_response,\n                sformat=\"dict\",\n                keyjar=self.keyjar,\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to parse response for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(authn_response, ErrorResponse):\n            error = authn_response.get(\"error\", \"\")\n            if error == \"login_required\":\n                logger.warning(\n                    \"User was not logged in (%s), trying again for %s\" % (error, state)\n                )\n                return self.create_authn_request(session)\n            else:\n                logger.error(\"Unable to process response %s for %s\" % (error, state))\n                raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"authn_response %s\" % authn_response)\n\n        if not authn_response.get(\"state\", None):\n            logger.error(\"State value not received from OP for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        if authn_response[\"state\"] != session.get(\"state\", None):\n            # this most likely means the user's Django session vanished\n            logger.error(\"Received state not the same as expected for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        if self.behaviour.get(\"response_type\") == \"code\":\n            # need an access token to get user info (and to log the user out later)\n            self._request_token(\n                authn_response[\"state\"], authn_response[\"code\"], session\n            )\n\n        user_info = self._get_user_info(state, session)\n\n        return user_info\n\n    def _get_user_info(self, state, session):\n        \"\"\"Get information from OP about the user.\"\"\"\n        scopes = list(self.behaviour.get(\"user_info_request\", []))\n        scopes.append(\"openid\")\n        try:\n            # get info about the user from OP\n            info_response = self.do_user_info_request(\n                state=session[\"state\"],\n                method=\"GET\",\n                scope=\" \".join(set(scopes)),\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to request user info for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(info_response, ErrorResponse):\n            logger.error(\n                \"Unable to get user info (%s) for %s\"\n                % (info_response.get(\"error\", \"\"), state)\n            )\n            raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"user info: %s\" % info_response)\n        return info_response.to_dict()\n\n    def _request_token(self, state, code, session):\n        \"\"\"Request a token from OP to allow us to then request user info.\"\"\"\n        try:\n            token_response = self.do_access_token_request(\n                scope=\"openid\",\n                state=state,\n                request_args={\n                    \"code\": code,\n                    \"redirect_uri\": self.registration_response[\"redirect_uris\"][0],\n                    \"client_id\": self.client_id,\n                    \"client_secret\": self.client_secret,\n                },\n                authn_method=self.registration_response[\"token_endpoint_auth_method\"],\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to obtain access token for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(token_response, ErrorResponse):\n            logger.error(\n                \"Unable to get token (%s) for %s\"\n                % (token_response.get(\"error\", \"\"), state)\n            )\n            raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"token response %s\" % token_response)\n\n        try:\n            # get the token and other bits of info\n            id_token = token_response[\"id_token\"]._dict\n\n            if id_token[\"nonce\"] != session[\"nonce\"]:\n                logger.error(\"Received nonce not the same as expected for %s\" % state)\n                raise o_e.AuthenticationFailed(locator=state)\n\n            session[\"id_token\"] = id_token\n            session[\"id_token_raw\"] = getattr(self, \"id_token_raw\", None)\n            session[\"access_token\"] = token_response[\"access_token\"]\n            session[\"refresh_token\"] = token_response.get(\"refresh_token\", \"\")\n            session[\"expires_in\"] = token_response.get(\"expires_in\", \"\")\n            self.id_token[state] = getattr(self, \"id_token_raw\", None)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to parse access token response for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n    def store_response(self, resp, info):\n        \"\"\"Make raw ID token available for internal use.\"\"\"\n        if isinstance(resp, AccessTokenResponse):\n            info = json.loads(info)\n            self.id_token_raw = info[\"id_token\"]\n\n        super(Client, self).store_response(resp, info)\n\n    def __repr__(self):\n        return \"Client {} {} {}\".format(\n            self.client_id,\n            self.client_prefs,\n            self.behaviour,\n        )\n", "path": "src/djangooidc/oidc.py"}], "after_files": [{"content": "# coding: utf-8\nfrom __future__ import unicode_literals\n\nimport logging\nimport json\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect\nfrom Cryptodome.PublicKey.RSA import importKey\nfrom jwkest.jwk import RSAKey  # type: ignore\nfrom oic import oic, rndstr, utils\nfrom oic.oauth2 import ErrorResponse\nfrom oic.oic import AuthorizationRequest, AuthorizationResponse, RegistrationResponse\nfrom oic.oic.message import AccessTokenResponse\nfrom oic.utils.authn.client import CLIENT_AUTHN_METHOD\nfrom oic.utils import keyio\n\nfrom . import exceptions as o_e\n\n__author__ = \"roland\"\n\nlogger = logging.getLogger(__name__)\n\n\nclass Client(oic.Client):\n    def __init__(self, op):\n        \"\"\"Step 1: Configure the OpenID Connect client.\"\"\"\n        logger.debug(\"Initializing the OpenID Connect client...\")\n        try:\n            provider = settings.OIDC_PROVIDERS[op]\n            verify_ssl = getattr(settings, \"OIDC_VERIFY_SSL\", True)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Configuration missing for OpenID Connect client\")\n            raise o_e.InternalError()\n\n        try:\n            # prepare private key for authentication method of private_key_jwt\n            key_bundle = keyio.KeyBundle()\n            rsa_key = importKey(provider[\"client_registration\"][\"sp_private_key\"])\n            key = RSAKey(key=rsa_key, use=\"sig\")\n            key_bundle.append(key)\n            keyjar = keyio.KeyJar(verify_ssl=verify_ssl)\n            keyjar.add_kb(\"\", key_bundle)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Key jar preparation failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n        try:\n            # create the oic client instance\n            super().__init__(\n                client_id=None,\n                client_authn_method=CLIENT_AUTHN_METHOD,\n                keyjar=keyjar,\n                settings=utils.settings.OicClientSettings(verify_ssl=verify_ssl),\n                config=None,\n            )\n            # must be set after client is initialized\n            self.behaviour = provider[\"behaviour\"]\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Client creation failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n        try:\n            # discover and store the provider (OP) urls, etc\n            self.provider_config(provider[\"srv_discovery_url\"])\n            self.store_registration_info(\n                RegistrationResponse(**provider[\"client_registration\"])\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\n                \"Provider info discovery failed for %s\",\n                provider[\"srv_discovery_url\"],\n            )\n            raise o_e.InternalError()\n\n    def create_authn_request(\n        self,\n        session,\n        extra_args=None,\n    ):\n        \"\"\"Step 2: Construct a login URL at OP's domain and send the user to it.\"\"\"\n        logger.debug(\"Creating the OpenID Connect authn request...\")\n        state = rndstr(size=32)\n        try:\n            session[\"state\"] = state\n            session[\"nonce\"] = rndstr(size=32)\n            scopes = list(self.behaviour.get(\"scope\", []))\n            scopes.append(\"openid\")\n            request_args = {\n                \"response_type\": self.behaviour.get(\"response_type\"),\n                \"scope\": \" \".join(set(scopes)),\n                \"state\": session[\"state\"],\n                \"nonce\": session[\"nonce\"],\n                \"redirect_uri\": self.registration_response[\"redirect_uris\"][0],\n                \"acr_values\": self.behaviour.get(\"acr_value\"),\n            }\n\n            if extra_args is not None:\n                request_args.update(extra_args)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to assemble request arguments for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        logger.debug(\"request args: %s\" % request_args)\n\n        try:\n            # prepare the request for sending\n            cis = self.construct_AuthorizationRequest(request_args=request_args)\n            logger.debug(\"request: %s\" % cis)\n\n            # obtain the url and headers from the prepared request\n            url, body, headers, cis = self.uri_and_body(\n                AuthorizationRequest,\n                cis,\n                method=\"GET\",\n                request_args=request_args,\n            )\n            logger.debug(\"body: %s\" % body)\n            logger.debug(\"URL: %s\" % url)\n            logger.debug(\"headers: %s\" % headers)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to prepare request for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        try:\n            # create the redirect object\n            response = HttpResponseRedirect(str(url))\n            # add headers to the object, if any\n            if headers:\n                for key, value in headers.items():\n                    response[key] = value\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Failed to create redirect object for %s\" % state)\n            raise o_e.InternalError(locator=state)\n\n        return response\n\n    def callback(self, unparsed_response, session):\n        \"\"\"Step 3: Receive OP's response, request an access token, and user info.\"\"\"\n        logger.debug(\"Processing the OpenID Connect callback response...\")\n        state = session.get(\"state\", \"\")\n        try:\n            # parse the response from OP\n            authn_response = self.parse_response(\n                AuthorizationResponse,\n                unparsed_response,\n                sformat=\"dict\",\n                keyjar=self.keyjar,\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to parse response for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(authn_response, ErrorResponse):\n            error = authn_response.get(\"error\", \"\")\n            if error == \"login_required\":\n                logger.warning(\n                    \"User was not logged in (%s), trying again for %s\" % (error, state)\n                )\n                return self.create_authn_request(session)\n            else:\n                logger.error(\"Unable to process response %s for %s\" % (error, state))\n                raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"authn_response %s\" % authn_response)\n\n        if not authn_response.get(\"state\", None):\n            logger.error(\"State value not received from OP for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        if authn_response[\"state\"] != session.get(\"state\", None):\n            # this most likely means the user's Django session vanished\n            logger.error(\"Received state not the same as expected for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        if self.behaviour.get(\"response_type\") == \"code\":\n            # need an access token to get user info (and to log the user out later)\n            self._request_token(\n                authn_response[\"state\"], authn_response[\"code\"], session\n            )\n\n        user_info = self._get_user_info(state, session)\n\n        return user_info\n\n    def _get_user_info(self, state, session):\n        \"\"\"Get information from OP about the user.\"\"\"\n        scopes = list(self.behaviour.get(\"user_info_request\", []))\n        scopes.append(\"openid\")\n        try:\n            # get info about the user from OP\n            info_response = self.do_user_info_request(\n                state=session[\"state\"],\n                method=\"GET\",\n                scope=\" \".join(set(scopes)),\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to request user info for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(info_response, ErrorResponse):\n            logger.error(\n                \"Unable to get user info (%s) for %s\"\n                % (info_response.get(\"error\", \"\"), state)\n            )\n            raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"user info: %s\" % info_response)\n        return info_response.to_dict()\n\n    def _request_token(self, state, code, session):\n        \"\"\"Request a token from OP to allow us to then request user info.\"\"\"\n        try:\n            token_response = self.do_access_token_request(\n                scope=\"openid\",\n                state=state,\n                request_args={\n                    \"code\": code,\n                    \"redirect_uri\": self.registration_response[\"redirect_uris\"][0],\n                    \"client_id\": self.client_id,\n                    \"client_secret\": self.client_secret,\n                },\n                authn_method=self.registration_response[\"token_endpoint_auth_method\"],\n                # There is a time desync issue between login.gov and cloud\n                # this addresses that by adding a clock skew.\n                skew=10,\n            )\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to obtain access token for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n        # ErrorResponse is not raised, it is passed back...\n        if isinstance(token_response, ErrorResponse):\n            logger.error(\n                \"Unable to get token (%s) for %s\"\n                % (token_response.get(\"error\", \"\"), state)\n            )\n            raise o_e.AuthenticationFailed(locator=state)\n\n        logger.debug(\"token response %s\" % token_response)\n\n        try:\n            # get the token and other bits of info\n            id_token = token_response[\"id_token\"]._dict\n\n            if id_token[\"nonce\"] != session[\"nonce\"]:\n                logger.error(\"Received nonce not the same as expected for %s\" % state)\n                raise o_e.AuthenticationFailed(locator=state)\n\n            session[\"id_token\"] = id_token\n            session[\"id_token_raw\"] = getattr(self, \"id_token_raw\", None)\n            session[\"access_token\"] = token_response[\"access_token\"]\n            session[\"refresh_token\"] = token_response.get(\"refresh_token\", \"\")\n            session[\"expires_in\"] = token_response.get(\"expires_in\", \"\")\n            self.id_token[state] = getattr(self, \"id_token_raw\", None)\n        except Exception as err:\n            logger.error(err)\n            logger.error(\"Unable to parse access token response for %s\" % state)\n            raise o_e.AuthenticationFailed(locator=state)\n\n    def store_response(self, resp, info):\n        \"\"\"Make raw ID token available for internal use.\"\"\"\n        if isinstance(resp, AccessTokenResponse):\n            info = json.loads(info)\n            self.id_token_raw = info[\"id_token\"]\n\n        super(Client, self).store_response(resp, info)\n\n    def __repr__(self):\n        return \"Client {} {} {}\".format(\n            self.client_id,\n            self.client_prefs,\n            self.behaviour,\n        )\n", "path": "src/djangooidc/oidc.py"}]}