Skip to content

Commit 56186cc

Browse files
committed
refactor: Taking into account Github issues 8 and 9
1 parent f50a07d commit 56186cc

File tree

5 files changed

+153
-44
lines changed

5 files changed

+153
-44
lines changed

.cz.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[tool.commitizen]
2+
name = "cz_conventional_commits"
3+
tag_format = "$version"
4+
version_scheme = "semver"
5+
version_provider = "composer"
6+
update_changelog_on_bump = true
7+
major_version_zero = true

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ A simple utility to use PKCE (Proof Key for Code Exchange) in PHP.
88
This little utility is intended to help people using Oauth2 with PKCE in PHP. It provides a simple way to generate a code verifier, a code challenge and to validate a code verifier with a code challenge.
99

1010
## Summary
11+
1112
- [Features](#features)
1213
- [Installation](#installation)
1314
- [Usage](#usage)
1415
- [License](#license)
1516

1617
## Features
18+
1719
- [x] Generate a code verifier
1820
- [x] Generate a code challenge from a given code verifier
1921
- [x] Generate a pair of code verifier and code challenge
@@ -22,12 +24,15 @@ This little utility is intended to help people using Oauth2 with PKCE in PHP. It
2224
> **Note:** All the code complies to the [RFC 7636](https://tools.ietf.org/html/rfc7636).
2325
2426
## Installation
27+
2528
Using composer:
29+
2630
```bash
2731
composer require adriengras/pkce-php
2832
```
2933

3034
## Usage
35+
3136
```php
3237
// import with composer autoloader
3338
use AdrienGras\PKCE\PKCE;
@@ -49,7 +54,7 @@ $pair = PKCEUtils::generateCodePair();
4954
$verifier = $pair['code_verifier'];
5055
$challenge = $pair['code_challenge'];
5156
// or with destructuring
52-
[$verifier, $challenge] = PKCEUtils::generateCodePair();
57+
['code_verifier' => $verifier, 'code_challenge' => $challenge] = PKCEUtils::generateCodePair();
5358

5459
// validate a code verifier with a code challenge
5560
$isValid = PKCEUtils::validate($verifier, $challenge);
@@ -58,4 +63,5 @@ $isValid = PKCEUtils::validate($verifier, $challenge);
5863
> **Note** You can also use the test case suite as a full example on how to use this utility. You can find it in the [tests](tests) folder.
5964
6065
## License
66+
6167
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

cliff.toml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# git-cliff ~ default configuration file
2+
# https://git-cliff.org/docs/configuration
3+
#
4+
# Lines starting with "#" are comments.
5+
# Configuration options are organized into tables and keys.
6+
# See documentation for more information on available options.
7+
8+
[changelog]
9+
# template for the changelog header
10+
header = """
11+
# Changelog\n
12+
All notable changes to this project will be documented in this file.\n
13+
"""
14+
# template for the changelog body
15+
# https://keats.github.io/tera/docs/#introduction
16+
body = """
17+
{% if version %}\
18+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
19+
{% else %}\
20+
## [unreleased]
21+
{% endif %}\
22+
{% for group, commits in commits | group_by(attribute="group") %}
23+
### {{ group | striptags | trim | upper_first }}
24+
{% for commit in commits %}
25+
- {% if commit.scope %}*({{ commit.scope }})* {% endif %}\
26+
{% if commit.breaking %}[**breaking**] {% endif %}\
27+
{{ commit.message | upper_first }}\
28+
{% endfor %}
29+
{% endfor %}\n
30+
"""
31+
# template for the changelog footer
32+
footer = """
33+
<!-- generated by git-cliff -->
34+
"""
35+
# remove the leading and trailing s
36+
trim = true
37+
# postprocessors
38+
postprocessors = [
39+
# { pattern = '<REPO>', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL
40+
]
41+
# render body even when there are no releases to process
42+
# render_always = true
43+
# output file path
44+
# output = "test.md"
45+
46+
[git]
47+
# parse the commits based on https://www.conventionalcommits.org
48+
conventional_commits = true
49+
# filter out the commits that are not conventional
50+
filter_unconventional = true
51+
# process each line of a commit as an individual commit
52+
split_commits = false
53+
# regex for preprocessing the commit messages
54+
commit_preprocessors = [
55+
# Replace issue numbers
56+
#{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](<REPO>/issues/${2}))"},
57+
# Check spelling of the commit with https://github.com/crate-ci/typos
58+
# If the spelling is incorrect, it will be automatically fixed.
59+
#{ pattern = '.*', replace_command = 'typos --write-changes -' },
60+
]
61+
# regex for parsing and grouping commits
62+
commit_parsers = [
63+
{ message = "^feat", group = "<!-- 0 -->🚀 Features" },
64+
{ message = "^fix", group = "<!-- 1 -->🐛 Bug Fixes" },
65+
{ message = "^doc", group = "<!-- 3 -->📚 Documentation" },
66+
{ message = "^perf", group = "<!-- 4 -->⚡ Performance" },
67+
{ message = "^refactor", group = "<!-- 2 -->🚜 Refactor" },
68+
{ message = "^style", group = "<!-- 5 -->🎨 Styling" },
69+
{ message = "^test", group = "<!-- 6 -->🧪 Testing" },
70+
{ message = "^chore\\(release\\): prepare for", skip = true },
71+
{ message = "^chore\\(deps.*\\)", skip = true },
72+
{ message = "^chore\\(pr\\)", skip = true },
73+
{ message = "^chore\\(pull\\)", skip = true },
74+
{ message = "^chore|^ci", group = "<!-- 7 -->⚙️ Miscellaneous Tasks" },
75+
{ body = ".*security", group = "<!-- 8 -->🛡️ Security" },
76+
{ message = "^revert", group = "<!-- 9 -->◀️ Revert" },
77+
]
78+
# filter out the commits that are not matched by commit parsers
79+
filter_commits = false
80+
# sort the tags topologically
81+
topo_order = false
82+
# sort the commits inside sections by oldest/newest order
83+
sort_commits = "oldest"

composer.json

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
{
2-
"name": "adriengras/pkce-php",
3-
"description": "A simple utility to use PKCE (Proof Key for Code Exchange) in PHP.",
4-
"type": "library",
5-
"license": "MIT",
6-
"autoload": {
7-
"psr-4": {
8-
"AdrienGras\\PKCE\\": "src/"
9-
}
10-
},
11-
"autoload-dev": {
12-
"psr-4": {
13-
"AdrienGras\\PKCE\\Tests\\": "tests/"
14-
}
15-
},
16-
"authors": [
17-
{
18-
"name": "Adrien Gras",
19-
"email": "agr@owlnext.fr"
20-
}
21-
],
22-
"minimum-stability": "stable",
23-
"require": {
24-
"php": ">=7.4"
25-
},
26-
"require-dev": {
27-
"symfony/test-pack": "^1.1"
2+
"name": "adriengras/pkce-php",
3+
"description": "A simple utility to use PKCE (Proof Key for Code Exchange) in PHP.",
4+
"type": "library",
5+
"license": "MIT",
6+
"autoload": {
7+
"psr-4": {
8+
"AdrienGras\\PKCE\\": "src/"
289
}
10+
},
11+
"autoload-dev": {
12+
"psr-4": {
13+
"AdrienGras\\PKCE\\Tests\\": "tests/"
14+
}
15+
},
16+
"authors": [
17+
{
18+
"name": "Adrien Gras",
19+
"email": "agr@owlnext.fr"
20+
}
21+
],
22+
"minimum-stability": "stable",
23+
"require": {
24+
"php": ">=7.4"
25+
},
26+
"require-dev": {
27+
"symfony/test-pack": "^1.1.0"
28+
}
2929
}

src/PKCEUtils.php

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,49 +24,64 @@ class PKCEUtils
2424
*/
2525
public const CODE_CHALLENGE_METHOD_S256 = 'S256';
2626

27+
// 66 characters, differs from the urlsafe base64 charset
28+
private const PKCE_VERIFIER_CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
29+
30+
2731
/**
28-
* Generate a code verifier.
29-
* @see https://tools.ietf.org/html/rfc7636#section-4.1
32+
* Generate a code verifier given a length. (default: 64)
33+
* @param int $length The length of the code verifier. It must be between 43 and 128.
3034
* @return string The code verifier.
3135
* @throws Exception if an appropriate source of randomness cannot be found.
36+
* @see https://tools.ietf.org/html/rfc7636#section-4.1
37+
* @see https://github.com/AdrienGras/pkce-php/issues/8
3238
*/
33-
public static function generateCodeVerifier(): string
39+
public static function generateCodeVerifier(int $length = 64): string
3440
{
35-
return rtrim(strtr(base64_encode(random_bytes(64)), '+/', '-_'), '=');
41+
$str = "";
42+
43+
if ($length < 43 || $length > 128) {
44+
throw new \InvalidArgumentException('The length of the code verifier must be between 43 and 128. See https://tools.ietf.org/html/rfc7636#section-4.1');
45+
}
46+
47+
for ($i = 0; $i < $length; $i++) {
48+
$str .= self::PKCE_VERIFIER_CHARSET[random_int(0, strlen(self::PKCE_VERIFIER_CHARSET) - 1)];
49+
}
50+
51+
return $str;
3652
}
3753

3854
/**
3955
* Derive a code challenge from a code verifier.
4056
* @param string $codeVerifier The code verifier to derive a code challenge from.
4157
* @param string $codeChallengeMethod The code challenge method to use. Use one of the constants from this class.
4258
* @return string The code challenge.
59+
* @see https://github.com/AdrienGras/pkce-php/issues/8
4360
*/
4461
public static function generateCodeChallenge(
4562
string $codeVerifier,
4663
string $codeChallengeMethod = self::CODE_CHALLENGE_METHOD_S256
47-
): string
48-
{
64+
): string {
4965
if (false === in_array($codeChallengeMethod, self::supportedCodeChallengeMethods())) {
5066
throw new \InvalidArgumentException(sprintf('Code challenge method "%s" is not supported.', $codeChallengeMethod));
5167
}
5268

5369
if (self::CODE_CHALLENGE_METHOD_PLAIN === $codeChallengeMethod) {
70+
// quick exit, since there is no transformation
5471
return $codeVerifier;
5572
}
5673

57-
if (self::CODE_CHALLENGE_METHOD_S256 === $codeChallengeMethod) {
58-
$codeChallengeMethod = 'sha256';
59-
}
60-
61-
return rtrim(strtr(base64_encode(hash($codeChallengeMethod, $codeVerifier, true)), '+/', '-_'), '=');
74+
$hash = hash('sha256', $codeVerifier, true);
75+
return sodium_bin2base64($hash, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
6276
}
6377

6478
/**
6579
* Generate a code verifier and a code challenge.
6680
* @return array The code verifier and the code challenge.
6781
* @throws Exception if an appropriate source of randomness cannot be found.
6882
*/
69-
public static function generateCodePair(string $codeChallengeMethod = self::CODE_CHALLENGE_METHOD_S256): array {
83+
public static function generateCodePair(string $codeChallengeMethod = self::CODE_CHALLENGE_METHOD_S256): array
84+
{
7085
$verifier = self::generateCodeVerifier();
7186

7287
return [
@@ -86,9 +101,8 @@ public static function validate(
86101
string $codeVerifier,
87102
string $codeChallenge,
88103
string $codeChallengeMethod = self::CODE_CHALLENGE_METHOD_S256
89-
): bool
90-
{
91-
return $codeChallenge === self::generateCodeChallenge($codeVerifier, $codeChallengeMethod);
104+
): bool {
105+
return hash_equals($codeChallenge, self::generateCodeChallenge($codeVerifier, $codeChallengeMethod));
92106
}
93107

94108
/**
@@ -102,5 +116,4 @@ public static function supportedCodeChallengeMethods(): array
102116
self::CODE_CHALLENGE_METHOD_S256,
103117
];
104118
}
105-
106-
}
119+
}

0 commit comments

Comments
 (0)