Skip to content
This repository was archived by the owner on Jun 23, 2023. It is now read-only.

Commit 0be7aa4

Browse files
authored
Merge pull request #115 from nsklikas/pkce-per-client
Add pkce essential per client
2 parents ce04493 + a98696e commit 0be7aa4

3 files changed

Lines changed: 69 additions & 9 deletions

File tree

docs/source/contents/conf.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,27 @@ An example::
8484
}
8585
}
8686

87+
The provided add-ons can be seen in the following sections.
88+
89+
pkce
90+
####
91+
92+
The pkce add on is activated using the ``oidcop.oidc.add_on.pkce.add_pkce_support``
93+
function. The possible configuration options can be found below.
94+
95+
essential
96+
---------
97+
98+
Whether pkce is mandatory, authentication requests without a ``code_challenge``
99+
will fail if this is True. This option can be overridden per client by defining
100+
``pkce_essential`` in the client metadata.
101+
102+
code_challenge_method
103+
---------------------
104+
105+
The allowed code_challenge methods. The supported code challenge methods are:
106+
``plain, S256, S384, S512``
107+
87108
--------------
88109
authentication
89110
--------------

src/oidcop/oidc/add_on/pkce.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from typing import Dict
44

55
from cryptojwt.utils import b64e
6-
from oidcmsg.oauth2 import (
7-
AuthorizationErrorResponse,
8-
RefreshAccessTokenRequest,
9-
TokenExchangeRequest,
10-
)
6+
from oidcmsg.oauth2 import AuthorizationErrorResponse
7+
from oidcmsg.oauth2 import RefreshAccessTokenRequest
8+
from oidcmsg.oauth2 import TokenExchangeRequest
119
from oidcmsg.oidc import TokenErrorResponse
1210

1311
from oidcop.endpoint import Endpoint
@@ -41,7 +39,14 @@ def post_authn_parse(request, client_id, endpoint_context, **kwargs):
4139
:param kwargs:
4240
:return:
4341
"""
44-
if endpoint_context.args["pkce"]["essential"] and "code_challenge" not in request:
42+
client = endpoint_context.cdb[client_id]
43+
if "pkce_essential" in client:
44+
essential = client["pkce_essential"]
45+
else:
46+
essential = endpoint_context.args["pkce"].get(
47+
"essential", False
48+
)
49+
if essential and "code_challenge" not in request:
4550
return AuthorizationErrorResponse(
4651
error="invalid_request", error_description="Missing required code_challenge",
4752
)
@@ -131,9 +136,6 @@ def add_pkce_support(endpoint: Dict[str, Endpoint], **kwargs):
131136
authn_endpoint.post_parse_request.append(post_authn_parse)
132137
token_endpoint.post_parse_request.append(post_token_parse)
133138

134-
if "essential" not in kwargs:
135-
kwargs["essential"] = False
136-
137139
code_challenge_methods = kwargs.get("code_challenge_methods", CC_METHOD.keys())
138140

139141
kwargs["code_challenge_methods"] = {}

tests/test_33_oauth2_pkce.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from . import full_path
88
from oidcop.configure import ASConfiguration
9+
910
import pytest
1011
import yaml
1112
from oidcmsg.message import Message
@@ -16,6 +17,7 @@
1617
from oidcmsg.oidc import TokenErrorResponse
1718

1819
import oidcop.oauth2.introspection
20+
from oidcop.configure import ASConfiguration
1921
from oidcop.configure import OPConfiguration
2022
from oidcop.cookie_handler import CookieHandler
2123
from oidcop.endpoint import Endpoint
@@ -290,6 +292,41 @@ def test_not_essential(self, conf):
290292

291293
assert isinstance(_req, Message)
292294

295+
def test_essential_per_client(self, conf):
296+
conf["add_on"]["pkce"]["kwargs"]["essential"] = False
297+
server = create_server(conf)
298+
authn_endpoint = server.server_get("endpoint", "authorization")
299+
token_endpoint = server.server_get("endpoint", "token")
300+
_authn_req = AUTH_REQ.copy()
301+
endpoint_context = server.server_get("endpoint_context")
302+
endpoint_context.cdb[AUTH_REQ["client_id"]]["pkce_essential"] = True
303+
304+
_pr_resp = authn_endpoint.parse_request(_authn_req.to_dict())
305+
306+
assert isinstance(_pr_resp, AuthorizationErrorResponse)
307+
assert _pr_resp["error"] == "invalid_request"
308+
assert _pr_resp["error_description"] == "Missing required code_challenge"
309+
310+
def test_not_essential_per_client(self, conf):
311+
conf["add_on"]["pkce"]["kwargs"]["essential"] = True
312+
server = create_server(conf)
313+
authn_endpoint = server.server_get("endpoint", "authorization")
314+
token_endpoint = server.server_get("endpoint", "token")
315+
_authn_req = AUTH_REQ.copy()
316+
endpoint_context = server.server_get("endpoint_context")
317+
endpoint_context.cdb[AUTH_REQ["client_id"]]["pkce_essential"] = False
318+
319+
_pr_resp = authn_endpoint.parse_request(_authn_req.to_dict())
320+
resp = authn_endpoint.process_request(_pr_resp)
321+
322+
assert isinstance(resp["response_args"], AuthorizationResponse)
323+
324+
_token_request = TOKEN_REQ.copy()
325+
_token_request["code"] = resp["response_args"]["code"]
326+
_req = token_endpoint.parse_request(_token_request)
327+
328+
assert isinstance(_req, Message)
329+
293330
def test_unknown_code_challenge_method(self):
294331
_authn_req = AUTH_REQ.copy()
295332
_authn_req["code_challenge"] = "aba"

0 commit comments

Comments
 (0)