Skip to content

Commit 38f7ac2

Browse files
committed
fix: run doctest on usage docs
1 parent 140af5a commit 38f7ac2

File tree

2 files changed

+84
-72
lines changed

2 files changed

+84
-72
lines changed

docs/usage.rst

Lines changed: 83 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ RSA encoding and decoding require the ``cryptography`` module. See :ref:`install
2222
.. code-block:: pycon
2323
2424
>>> import jwt
25-
>>> private_key = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..."
26-
>>> public_key = b"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEAC..."
25+
>>> private_key = b"-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwhvqCC+37A+UXgcvDl+7nbVjDI3QErdZBkI1VypVBMkKKWHM\nNLMdHk0bIKL+1aDYTRRsCKBy9ZmSSX1pwQlO/3+gRs/MWG27gdRNtf57uLk1+lQI\n6hBDozuyBR0YayQDIx6VsmpBn3Y8LS13p4pTBvirlsdX+jXrbOEaQphn0OdQo0WD\noOwwsPCNCKoIMbUOtUCowvjesFXlWkwG1zeMzlD1aDDS478PDZdckPjT96ICzqe4\nO1Ok6fRGnor2UTmuPy0f1tI0F7Ol5DHAD6pZbkhB70aTBuWDGLDR0iLenzyQecmD\n4aU19r1XC9AHsVbQzxHrP8FveZGlV/nJOBJwFwIDAQABAoIBAFCVFBA39yvJv/dV\nFiTqe1HahnckvFe4w/2EKO65xTfKWiyZzBOotBLrQbLH1/FJ5+H/82WVboQlMATQ\nSsH3olMRYbFj/NpNG8WnJGfEcQpb4Vu93UGGZP3z/1B+Jq/78E15Gf5KfFm91PeQ\nY5crJpLDU0CyGwTls4ms3aD98kNXuxhCGVbje5lCARizNKfm/+2qsnTYfKnAzN+n\nnm0WCjcHmvGYO8kGHWbFWMWvIlkoZ5YubSX2raNeg+YdMJUHz2ej1ocfW0A8/tmL\nwtFoBSuBe1Z2ykhX4t6mRHp0airhyc+MO0bIlW61vU/cPGPos16PoS7/V08S7ZED\nX64rkyECgYEA4iqeJZqny/PjOcYRuVOHBU9nEbsr2VJIf34/I9hta/mRq8hPxOdD\n/7ES/ZTZynTMnOdKht19Fi73Sf28NYE83y5WjGJV/JNj5uq2mLR7t2R0ZV8uK8tU\n4RR6b2bHBbhVLXZ9gqWtu9bWtsxWOkG1bs0iONgD3k5oZCXp+IWuklECgYEA27bA\n7UW+iBeB/2z4x1p/0wY+whBOtIUiZy6YCAOv/HtqppsUJM+W9GeaiMpPHlwDUWxr\n4xr6GbJSHrspkMtkX5bL9e7+9zBguqG5SiQVIzuues9Jio3ZHG1N2aNrr87+wMiB\nxX6Cyi0x1asmsmIBO7MdP/tSNB2ebr8qM6/6mecCgYBA82ZJfFm1+8uEuvo6E9/R\nyZTbBbq5BaVmX9Y4MB50hM6t26/050mi87J1err1Jofgg5fmlVMn/MLtz92uK/hU\nS9V1KYRyLc3h8gQQZLym1UWMG0KCNzmgDiZ/Oa/sV5y2mrG+xF/ZcwBkrNgSkO5O\n7MBoPLkXrcLTCARiZ9nTkQKBgQCsaBGnnkzOObQWnIny1L7s9j+UxHseCEJguR0v\nXMVh1+5uYc5CvGp1yj5nDGldJ1KrN+rIwMh0FYt+9dq99fwDTi8qAqoridi9Wl4t\nIXc8uH5HfBT3FivBtLucBjJgOIuK90ttj8JNp30tbynkXCcfk4NmS23L21oRCQyy\nlmqNDQKBgQDRvzEB26isJBr7/fwS0QbuIlgzEZ9T3ZkrGTFQNfUJZWcUllYI0ptv\ny7ShHOqyvjsC3LPrKGyEjeufaM5J8EFrqwtx6UB/tkGJ2bmd1YwOWFHvfHgHCZLP\n34ZNURCvxRV9ZojS1zmDRBJrSo7+/K0t28hXbiaTOjJA18XAyyWmGg==\n-----END RSA PRIVATE KEY-----\n"
26+
>>> public_key = b"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhvqCC+37A+UXgcvDl+7\nnbVjDI3QErdZBkI1VypVBMkKKWHMNLMdHk0bIKL+1aDYTRRsCKBy9ZmSSX1pwQlO\n/3+gRs/MWG27gdRNtf57uLk1+lQI6hBDozuyBR0YayQDIx6VsmpBn3Y8LS13p4pT\nBvirlsdX+jXrbOEaQphn0OdQo0WDoOwwsPCNCKoIMbUOtUCowvjesFXlWkwG1zeM\nzlD1aDDS478PDZdckPjT96ICzqe4O1Ok6fRGnor2UTmuPy0f1tI0F7Ol5DHAD6pZ\nbkhB70aTBuWDGLDR0iLenzyQecmD4aU19r1XC9AHsVbQzxHrP8FveZGlV/nJOBJw\nFwIDAQAB\n-----END PUBLIC KEY-----\n"
2727
>>> encoded = jwt.encode({"some": "payload"}, private_key, algorithm="RS256")
2828
>>> print(encoded)
29-
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
30-
>>> decoded = jwt.decode(encoded, public_key, algorithms=["RS256"])
29+
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.ACNvAmKejouaO7fOVAqiqTQ2nE7jtvA2hpUJk3XduO9qp1mhCSJGIMQRNokyzNZgJFibGLZhcfwf0_4amlYqwBQ3HTB58zDdV-iAoSIxnvOERMc9qxUKNupAs4B0aL0vzIvpIMoDxIMDAnNviDIDzWPnprnpwHNdza7Y40_O1h4trmJcstE0xjmcyV0CEemwOCSARcFnxVK8rE5dItdL05IWrzOx-twBPH5jIU9zwtDvyci9LXUpG010CiejDxI1Iu0ezbN8iX2wkNVbmsYPNnMvgmGCMJbSiatzXJNDdrGXM_YiJKxXvOHcL6aUDYM0vc5oymT4-aAHwKEUB8592A
30+
>>> jwt.decode(encoded, public_key, algorithms=["RS256"])
3131
{'some': 'payload'}
3232
3333
If your private key needs a passphrase, you need to pass in a ``PrivateKey`` object from ``cryptography``.
@@ -37,8 +37,8 @@ If your private key needs a passphrase, you need to pass in a ``PrivateKey`` obj
3737
from cryptography.hazmat.primitives import serialization
3838
from cryptography.hazmat.backends import default_backend
3939
40-
pem_bytes = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..."
41-
passphrase = b"your password"
40+
pem_bytes = b"-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,C9C8F89EC68D15F26EB9B9695216C6DC\nE3lvX0dYjDxC0DIDitwNj+mEvU48Cqlp9esIeVmfcFmM6KpuQEA4asg/19kldbRq\ntOAYwmMuzz6GNYtX6sQXcStUE3pKMiMaTuP9WXzTc0boSYsGpGoQLtGv3h+0lkPu\nTGaktEhIfplAYlmsS/twr9Jh9QZjEs3dEMwpuF8A/iDZFeIE2thZL0bo38VWorgZ\nTCoOlC7qGtaeDvXXYrMvAUw3lN9A+DvxuPvbGqfqiHVBhxRcQEcR5p65lKP/V0WQ\nDe0AqCx1ghYGnExT7I4GLfr7Ux3F1UcVldPPsNeCTR/5YMOYDw7o5CZZ2TM39T33\nDBwfRhDqKe4bMUQcvcD54S2tfW7tEekm6mx5JwzW11sd0Gprj2uggDTOj3ce2yzM\nzl/dfbyFgh6v4jFeblIgvQ4VPg9nfCaRhatw5KXnfHBvmvdxlQ1Qp5P43ThXjI2a\njaJdm2lu1DLhf1OYGeQ0ytDDPzvhrZrdEJ8jbB3VCn4O/hvCtdsp7jVw2Djxmw2A\niRz2zlZJUlaytbi/DMpEVFwIzpuiDkpJ+ekzAsBbm/rGR/tjCEtHzVuoQNUWI93k\n0FML+Zzb6AkBWYjBXDZtzwJpMdNr8Vvh3krZySbRzQstqL2PYuNoSZ8/1xnnVqTV\nA0pDX7OS856AXQzQ1FRjjk/Jd0k6jGj8d7LzVgMnb8VknKvshlLmZDz8Sqa1coN4\n0Z1VfiT0Hzlk0fkoGtRjhSc3MB6ZLg7vVlY5vb4bRrTX79s/p8Y/OecYnGC6qhTi\n+VyJiMfwXyjFjIWYH8Y3G0QLkvOrTxLAY/3B2TU5wVSD7lfnPKOatMK1W0DHu5jp\nG9PPTzK9ol3v6Pk0prYg1fiApb6CCBUeZBvCIbJCzYrL/yBV/xYlCwAekLNGz9Vj\nNQUoiJqi27fOQi+ZXCrF7gYj8afo/xrg0tf7YqoOty8qfsozXzqwHKn+PcZOcqa5\n5rIqjLOO2f6KO2dxBeZK6zmzg7K/8RjvsNkEuXffec/nwnC10OVoMbE4wyPmNUQi\ndSuZ6xWBqiREjodLL+Ez/N1Qa52kuLSigrrSBTM2e42PWDV1sNW5V2wwlnolXFF6\n2Xp74WaGdnwF4Afrm7AnaBxdmfjk/a+c2uzPkZkpVnxrW3l8afphhKpRoTLzqDPp\nZGc5Fx9UZsmX18B8D1OGbf4aVLUkoqPPHbccCI+wByoAgIoq+y2391fP/Db6fY9A\nR4t2uuP2sNqDfYtzPYikePBXhYlldE1UHJ378g8pTiRHOI9BhuKIOIbVngPUYk4I\nwhYct2K84HjvR3iRnobK0UmmNOqtK0AtUqne+xaj1f3OwMZSvTUe7/jESgw1e1tn\nulKiWnKnmTSZkeTIp6itui2T7ewfNyitPtvnhoH1fBnMyUVACip0SLXp1fwQ7iCc\namPFFKo7p+C7P3l0ItegaMHywOSTBvK39DQTIpF9ml8VCQ+UyPOv/LnSJk1mbJN/\nc2Hdoj5dMa6T7ysIwZGEissJ/MEP+dpRs7VmCjWrHCDHfeAIO0n32g4zbzlNc/OA\nIdCXTvi4xUEn2n3JPt5Ba9qDUevaHSERlLxI+9a4ZaZeg4t+AzY0ur6+RWx+PaXB\n-----END RSA PRIVATE KEY-----\n"
41+
passphrase = b"abc123"
4242
4343
private_key = serialization.load_pem_private_key(
4444
pem_bytes, password=passphrase, backend=default_backend()
@@ -60,7 +60,7 @@ Specifying Additional Headers
6060
... algorithm="HS256",
6161
... headers={"kid": "230498151c214b788dd97f22b85410a5"},
6262
... )
63-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJzb21lIjoicGF5bG9hZCJ9.DogbDGmMHgA_bU05TAB-R6geQ2nMU2BRM-LnYEtefwg'
63+
'eyJhbGciOiJIUzI1NiIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1IiwidHlwIjoiSldUIn0.eyJzb21lIjoicGF5bG9hZCJ9.0n16c-shKKnw6gervyk1Dge35tvzbzQ_KCV3H3bgoJ0'
6464
6565
6666
Reading the Claimset without Validation
@@ -90,8 +90,14 @@ key in the header.
9090

9191
.. code-block:: pycon
9292
93+
>>> encoded = jwt.encode(
94+
... {"some": "payload"},
95+
... "secret",
96+
... algorithm="HS256",
97+
... headers={"kid": "230498151c214b788dd97f22b85410a5"},
98+
... )
9399
>>> jwt.get_unverified_header(encoded)
94-
{'alg': 'RS256', 'typ': 'JWT', 'kid': 'key-id-12345...'}
100+
{'alg': 'HS256', 'kid': '230498151c214b788dd97f22b85410a5', 'typ': 'JWT'}
95101
96102
Registered Claim Names
97103
----------------------
@@ -119,21 +125,23 @@ Expiration Time Claim (exp)
119125
You can pass the expiration time as a UTC UNIX timestamp (an int) or as a
120126
datetime, which will be converted into an int. For example:
121127

122-
.. code-block:: python
128+
.. code-block:: pycon
123129
124-
jwt.encode({"exp": 1371720939}, "secret")
125-
jwt.encode({"exp": datetime.now(tz=timezone.utc)}, "secret")
130+
>>> from datetime import datetime, timezone
131+
>>> token = jwt.encode({"exp": 1371720939}, "secret")
132+
>>> token = jwt.encode({"exp": datetime.now(tz=timezone.utc)}, "secret")
126133
127134
Expiration time is automatically verified in `jwt.decode()` and raises
128135
`jwt.ExpiredSignatureError` if the expiration time is in the past:
129136

130-
.. code-block:: python
137+
.. code-block:: pycon
131138
132-
try:
133-
jwt.decode("JWT_STRING", "secret", algorithms=["HS256"])
134-
except jwt.ExpiredSignatureError:
135-
# Signature has expired
136-
...
139+
>>> try:
140+
... jwt.decode(token, "secret", algorithms=["HS256"])
141+
... except jwt.ExpiredSignatureError:
142+
... print("expired")
143+
...
144+
expired
137145
138146
Expiration time will be compared to the current UTC time (as given by
139147
`timegm(datetime.now(tz=timezone.utc).utctimetuple())`), so be sure to use a UTC timestamp
@@ -147,27 +155,25 @@ For example, if you have a JWT payload with a expiration time set to 30 seconds
147155
after creation but you know that sometimes you will process it after 30 seconds,
148156
you can set a leeway of 10 seconds in order to have some margin:
149157

150-
.. code-block:: python
151-
152-
jwt_payload = jwt.encode(
153-
{"exp": datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(seconds=30)},
154-
"secret",
155-
)
156-
157-
time.sleep(32)
158+
.. code-block:: pycon
158159
159-
# JWT payload is now expired
160-
# But with some leeway, it will still validate
161-
jwt.decode(jwt_payload, "secret", leeway=10, algorithms=["HS256"])
160+
>>> import time, datetime
161+
>>> from datetime import timezone
162+
>>> payload = {"exp": datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(seconds=1)}
163+
>>> token = jwt.encode(payload, "secret")
164+
>>> time.sleep(2)
165+
>>> # JWT payload is now expired
166+
>>> # But with some leeway, it will still validate
167+
>>> decoded = jwt.decode(token, "secret", leeway=5, algorithms=["HS256"])
162168
163169
Instead of specifying the leeway as a number of seconds, a `datetime.timedelta`
164170
instance can be used. The last line in the example above is equivalent to:
165171

166-
.. code-block:: python
172+
.. code-block:: pycon
167173
168-
jwt.decode(
169-
jwt_payload, "secret", leeway=datetime.timedelta(seconds=10), algorithms=["HS256"]
170-
)
174+
>>> decoded = jwt.decode(
175+
... token, "secret", leeway=datetime.timedelta(seconds=10), algorithms=["HS256"]
176+
... )
171177
172178
Not Before Time Claim (nbf)
173179
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -182,10 +188,10 @@ Not Before Time Claim (nbf)
182188

183189
The `nbf` claim works similarly to the `exp` claim above.
184190

185-
.. code-block:: python
191+
.. code-block:: pycon
186192
187-
jwt.encode({"nbf": 1371720939}, "secret")
188-
jwt.encode({"nbf": datetime.now(tz=timezone.utc)}, "secret")
193+
>>> token = jwt.encode({"nbf": 1371720939}, "secret")
194+
>>> token = jwt.encode({"nbf": datetime.datetime.now(tz=timezone.utc)}, "secret")
189195
190196
Issuer Claim (iss)
191197
~~~~~~~~~~~~~~~~~~
@@ -195,12 +201,16 @@ Issuer Claim (iss)
195201
The "iss" value is a case-sensitive string containing a StringOrURI
196202
value. Use of this claim is OPTIONAL.
197203

198-
.. code-block:: python
199-
200-
payload = {"some": "payload", "iss": "urn:foo"}
204+
.. code-block:: pycon
201205
202-
token = jwt.encode(payload, "secret")
203-
decoded = jwt.decode(token, "secret", issuer="urn:foo", algorithms=["HS256"])
206+
>>> payload = {"some": "payload", "iss": "urn:foo"}
207+
>>> token = jwt.encode(payload, "secret")
208+
>>> try:
209+
... jwt.decode(token, "secret", issuer="urn:invalid", algorithms=["HS256"])
210+
... except jwt.InvalidIssuerError:
211+
... print("invalid issuer")
212+
...
213+
invalid issuer
204214
205215
If the issuer claim is incorrect, `jwt.InvalidIssuerError` will be raised.
206216

@@ -217,34 +227,36 @@ Audience Claim (aud)
217227
In the general case, the "aud" value is an array of case-
218228
sensitive strings, each containing a StringOrURI value.
219229

220-
.. code-block:: python
221-
222-
payload = {"some": "payload", "aud": ["urn:foo", "urn:bar"]}
230+
.. code-block:: pycon
223231
224-
token = jwt.encode(payload, "secret")
225-
decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
232+
>>> payload = {"some": "payload", "aud": ["urn:foo", "urn:bar"]}
233+
>>> token = jwt.encode(payload, "secret")
234+
>>> decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
235+
>>> decoded = jwt.decode(token, "secret", audience="urn:bar", algorithms=["HS256"])
226236
227237
In the special case when the JWT has one audience, the "aud" value MAY be
228238
a single case-sensitive string containing a StringOrURI value.
229239

230-
.. code-block:: python
231-
232-
payload = {"some": "payload", "aud": "urn:foo"}
240+
.. code-block:: pycon
233241
234-
token = jwt.encode(payload, "secret")
235-
decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
242+
>>> payload = {"some": "payload", "aud": "urn:foo"}
243+
>>> token = jwt.encode(payload, "secret")
244+
>>> decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"])
236245
237246
If multiple audiences are accepted, the ``audience`` parameter for
238247
``jwt.decode`` can also be an iterable
239248

240-
.. code-block:: python
249+
.. code-block:: pycon
241250
242-
payload = {"some": "payload", "aud": "urn:foo"}
243-
244-
token = jwt.encode(payload, "secret")
245-
decoded = jwt.decode(
246-
token, "secret", audience=["urn:foo", "urn:bar"], algorithms=["HS256"]
247-
)
251+
>>> payload = {"some": "payload", "aud": "urn:foo"}
252+
>>> token = jwt.encode(payload, "secret")
253+
>>> decoded = jwt.decode(token, "secret", audience=["urn:foo", "urn:bar"], algorithms=["HS256"])
254+
>>> try:
255+
... jwt.decode(token, "secret", audience=["urn:invalid"], algorithms=["HS256"])
256+
... except jwt.InvalidAudienceError:
257+
... print("invalid audience")
258+
...
259+
invalid audience
248260
249261
The interpretation of audience values is generally application specific.
250262
Use of this claim is OPTIONAL.
@@ -260,10 +272,10 @@ Issued At Claim (iat)
260272

261273
If the `iat` claim is not a number, an `jwt.InvalidIssuedAtError` exception will be raised.
262274

263-
.. code-block:: python
275+
.. code-block:: pycon
264276
265-
jwt.encode({"iat": 1371720939}, "secret")
266-
jwt.encode({"iat": datetime.now(tz=timezone.utc)}, "secret")
277+
>>> token = jwt.encode({"iat": 1371720939}, "secret")
278+
>>> token = jwt.encode({"iat": datetime.datetime.now(tz=timezone.utc)}, "secret")
267279
268280
Requiring Presence of Claims
269281
----------------------------
@@ -272,8 +284,13 @@ If you wish to require one or more claims to be present in the claimset, you can
272284

273285
.. code-block:: pycon
274286
275-
>>> jwt.decode(encoded, options={"require": ["exp", "iss", "sub"]})
276-
{'exp': 1371720939, 'iss': 'urn:foo', 'sub': '25c37522-f148-4cbf-8ee6-c4a9718dd0af'}
287+
>>> token = jwt.encode({"sub":"1234567890","iat":1371720939}, "secret")
288+
>>> try:
289+
... jwt.decode(token, "secret", options={"require": ["exp", "iss", "sub"]}, algorithms=["HS256"])
290+
... except jwt.MissingRequiredClaimError as e:
291+
... print(e)
292+
...
293+
Token is missing the "exp" claim
277294
278295
Retrieve RSA signing keys from a JWKS endpoint
279296
----------------------------------------------
@@ -288,13 +305,13 @@ Retrieve RSA signing keys from a JWKS endpoint
288305
>>> optional_custom_headers = {"User-agent": "custom-user-agent"}
289306
>>> jwks_client = PyJWKClient(url, headers=optional_custom_headers)
290307
>>> signing_key = jwks_client.get_signing_key_from_jwt(token)
291-
>>> data = jwt.decode(
308+
>>> jwt.decode(
292309
... token,
293310
... signing_key,
294311
... audience="https://expenses-api",
295312
... options={"verify_exp": False},
313+
... algorithms=["RS256"])
296314
... )
297-
>>> print(data)
298315
{'iss': 'https://dev-87evx9ru.auth0.com/', 'sub': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC@clients', 'aud': 'https://expenses-api', 'iat': 1572006954, 'exp': 1572006964, 'azp': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC', 'gty': 'client-credentials'}
299316
300317
OIDC Login Flow
@@ -314,7 +331,6 @@ is not built into pyjwt.
314331
import jwt
315332
import requests
316333
317-
318334
# Part 1: setup
319335
# get the OIDC config and JWKs to use
320336
@@ -324,15 +340,12 @@ is not built into pyjwt.
324340
# example of fetching data from your OIDC server
325341
# see: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
326342
oidc_server = ...
327-
oidc_config = requests.get(
328-
f"https://{oidc_server}/.well-known/openid-configuration"
329-
).json()
343+
oidc_config = requests.get(f"https://{oidc_server}/.well-known/openid-configuration").json()
330344
signing_algos = oidc_config["id_token_signing_alg_values_supported"]
331345
332346
# setup a PyJWKClient to get the appropriate signing key
333347
jwks_client = jwt.PyJWKClient(oidc_config["jwks_uri"])
334348
335-
336349
# Part 2: login / authorization
337350
# when a user completes an OIDC login flow, there will be a well-formed
338351
# response object to parse/handle
@@ -343,7 +356,6 @@ is not built into pyjwt.
343356
id_token = token_response["id_token"]
344357
access_token = token_response["access_token"]
345358
346-
347359
# Part 3: decode and validate at_hash
348360
# after the login is complete, the id_token needs to be decoded
349361
# this is the stage at which an OIDC client must verify the at_hash
@@ -352,7 +364,7 @@ is not built into pyjwt.
352364
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
353365
354366
# now, decode_complete to get payload + header
355-
data = jwt.api_jwt.decode_complete(
367+
data = jwt.decode_complete(
356368
id_token,
357369
key=signing_key,
358370
audience=client_id,

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ extras = docs
4747
commands =
4848
sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html
4949
sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html
50-
python -m doctest README.rst
50+
python -m doctest README.rst docs/usage.rst
5151

5252

5353
[testenv:lint]

0 commit comments

Comments
 (0)