Skip to content

Commit a7e8600

Browse files
Merge pull request #16 from jasonraimondi/optional-config
Authorization server optional config cleanup
2 parents ca2a8db + 296fba9 commit a7e8600

File tree

8 files changed

+133
-22
lines changed

8 files changed

+133
-22
lines changed

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,32 @@ authorizationServer.enableGrantType("client_credentials", new DateInterval("5h")
154154
authorizationServer.enableGrantType("authorization_code", new DateInterval("2h"));
155155
```
156156

157+
The authorization server has a few optional settings with the following default values;
158+
159+
```typescript
160+
AuthorizationServerOptions {
161+
requiresPKCE: true;
162+
useUrlEncode: true;
163+
}
164+
```
165+
166+
To configure these options, pass the value in as the last argument:
167+
168+
```typescript
169+
const authorizationServer = new AuthorizationServer(
170+
authCodeRepository,
171+
clientRepository,
172+
accessTokenRepository,
173+
scopeRepository,
174+
userRepository,
175+
new JwtService("secret-key"),
176+
{
177+
requiresPKCE: false, // default is true
178+
useUrlEncode: false, // default is true
179+
}
180+
);
181+
```
182+
157183
### Repositories
158184

159185
There are a few repositories you are going to need to implement in order to create an `AuthorizationServer`.

docs/getting_started/README.md

+47-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,53 @@ yarn add @jmondi/oauth2-server
1818
</code-block>
1919
</code-group>
2020

21-
### Getting Started
21+
## The Authorization Server
22+
23+
The AuthorizationServer depends on [the repositories](#repositories). By default, no grants are enabled; each grant is opt-in and must be enabled when creating the AuthorizationServer.
24+
25+
You can enable any grant types you would like to support.
26+
27+
```typescript
28+
const authorizationServer = new AuthorizationServer(
29+
authCodeRepository,
30+
clientRepository,
31+
accessTokenRepository,
32+
scopeRepository,
33+
userRepository,
34+
new JwtService("secret-key"),
35+
);
36+
authorizationServer.enableGrantType("client_credentials");
37+
authorizationServer.enableGrantType("authorization_code");
38+
authorizationServer.enableGrantType("refresh_token");
39+
authorizationServer.enableGrantType("implicit"); // implicit grant is not recommended
40+
authorizationServer.enableGrantType("password"); // password grant is not recommended
41+
```
42+
43+
The authorization server has a few optional settings with the following default values;
44+
45+
```typescript
46+
AuthorizationServerOptions {
47+
requiresPKCE: true;
48+
useUrlEncode: true;
49+
}
50+
```
51+
52+
To configure these options, pass the value in as the last argument:
53+
54+
```typescript
55+
const authorizationServer = new AuthorizationServer(
56+
authCodeRepository,
57+
clientRepository,
58+
accessTokenRepository,
59+
scopeRepository,
60+
userRepository,
61+
new JwtService("secret-key"),
62+
{
63+
requiresPKCE: false, // default is true
64+
useUrlEncode: false, // default is true
65+
}
66+
);
67+
```
2268

2369
## The Token Endpoint
2470

docs/grants/authorization_code.md

+35-5
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,27 @@ Pragma: no-cache
102102
```
103103
:::
104104

105-
### Code Verifier
105+
### PKCE
106+
107+
PKCE ([RFC 7636](https://tools.ietf.org/html/rfc7636)) is an extension to the [Authorization Code flow](https://oauth.net/2/grant-types/authorization-code/) to prevent several attacks and to be able to securely perform the OAuth exchange from public clients.
108+
109+
By default, PKCE is enabled and encouraged for all users. If you need to support a legacy client system without PKCE, you can disable PKCE with the authorization server:
110+
111+
```
112+
const authorizationServer = new AuthorizationServer(
113+
authCodeRepository,
114+
clientRepository,
115+
accessTokenRepository,
116+
scopeRepository,
117+
userRepository,
118+
new JwtService("secret-key"),
119+
{
120+
requiresPKCE: false,
121+
}
122+
);
123+
```
124+
125+
#### Code Verifier
106126

107127
The `code_verifier` is part of the extended [“PKCE”](https://tools.ietf.org/html/rfc7636) and helps mitigate the threat of having authorization codes intercepted.
108128

@@ -116,10 +136,20 @@ import crypto from "crypto";
116136
const code_verifier = crypto.randomBytes(43).toString("hex");
117137
```
118138

119-
https://www.oauth.com/oauth2-servers/pkce/authorization-request/
139+
@see [https://www.oauth.com/oauth2-servers/pkce/authorization-request/](https://www.oauth.com/oauth2-servers/pkce/authorization-request/)
140+
141+
::: tip
142+
You can opt out of the base64 url encode with the following [AuthorizationServer option](../getting_started/#the-authorization-server):
143+
144+
```typescript
145+
{
146+
useUrlEncode: false,
147+
}
148+
```
149+
:::
120150

121151

122-
### Code Challenge
152+
#### Code Challenge
123153

124154
Now we need to create a `code_challenge` from our `code_verifier`.
125155

@@ -138,7 +168,6 @@ Clients that do not have the ability to perform a SHA256 hash are permitted to u
138168
```typescript
139169
const code_challenge = code_verifier;
140170
```
141-
:::
142171

143172
::: details Need a base64urlencode function?
144173
```typescript
@@ -149,4 +178,5 @@ function base64urlencode(str: string) {
149178
.replace(/\//g, "_")
150179
.replace(/=/g, "");
151180
}
152-
```
181+
```
182+
:::

src/authorization_server.ts

+10-6
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,27 @@ export class AuthorizationServer {
6868
),
6969
};
7070

71+
private readonly options: AuthorizationServerOptions;
72+
7173
constructor(
7274
private readonly authCodeRepository: OAuthAuthCodeRepository,
7375
private readonly clientRepository: OAuthClientRepository,
7476
private readonly tokenRepository: OAuthTokenRepository,
7577
private readonly scopeRepository: OAuthScopeRepository,
7678
private readonly userRepository: OAuthUserRepository,
7779
private readonly jwt: JwtInterface,
78-
private readonly options: AuthorizationServerOptions = {
80+
options?: Partial<AuthorizationServerOptions>,
81+
) {
82+
this.options = {
7983
requiresPKCE: true,
80-
useUrlEncode: false,
81-
},
82-
) {}
84+
useUrlEncode: true,
85+
...options,
86+
}
87+
}
8388

8489
enableGrantType(grantType: GrantIdentifier, accessTokenTTL: DateInterval = new DateInterval("1h")): void {
8590
const grant = this.availableGrants[grantType];
86-
grant.requiresPKCE = this.options.requiresPKCE;
87-
grant.useUrlEncode = this.options.useUrlEncode;
91+
grant.options = this.options;
8892
this.enabledGrantTypes[grantType] = grant;
8993
this.grantTypeAccessTokenTTL[grantType] = accessTokenTTL;
9094
}

src/grants/abstract/abstract.grant.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AuthorizationServerOptions } from "../../authorization_server";
12
import { isClientConfidential, OAuthClient } from "../../entities/client.entity";
23
import { OAuthScope } from "../../entities/scope.entity";
34
import { OAuthToken } from "../../entities/token.entity";
@@ -32,8 +33,11 @@ export interface ITokenData {
3233
}
3334

3435
export abstract class AbstractGrant implements GrantInterface {
35-
public requiresPKCE = true;
36-
public useUrlEncode = true;
36+
public readonly options: AuthorizationServerOptions = {
37+
requiresPKCE: true,
38+
useUrlEncode: true,
39+
};
40+
3741
protected readonly scopeDelimiterString = " ";
3842

3943
protected readonly supportedGrantTypes: GrantIdentifier[] = [

src/grants/abstract/grant.interface.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AuthorizationServerOptions } from "../../authorization_server";
12
import { AuthorizationRequest } from "../../requests/authorization.request";
23
import { RequestInterface } from "../../requests/request";
34
import { ResponseInterface } from "../../responses/response";
@@ -6,8 +7,8 @@ import { DateInterval } from "../../utils/date_interval";
67
export type GrantIdentifier = "authorization_code" | "client_credentials" | "refresh_token" | "password" | "implicit";
78

89
export interface GrantInterface {
9-
requiresPKCE: boolean;
10-
useUrlEncode: boolean;
10+
options: AuthorizationServerOptions;
11+
1112
identifier: GrantIdentifier;
1213

1314
canRespondToAccessTokenRequest(request: RequestInterface): boolean;

src/grants/auth_code.grant.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class AuthCodeGrant extends AbstractAuthorizedGrant {
106106
verifier = this.codeChallengeVerifiers.S256;
107107
}
108108

109-
if (!verifier.verifyCodeChallenge(codeVerifier, validatedPayload.code_challenge, this.useUrlEncode)) {
109+
if (!verifier.verifyCodeChallenge(codeVerifier, validatedPayload.code_challenge, this.options.useUrlEncode)) {
110110
throw OAuthException.invalidGrant("Failed to verify code challenge.");
111111
}
112112
}
@@ -157,7 +157,7 @@ export class AuthCodeGrant extends AbstractAuthorizedGrant {
157157

158158
const codeChallenge = this.getQueryStringParameter("code_challenge", request);
159159

160-
if (this.requiresPKCE && !codeChallenge) {
160+
if (this.options.requiresPKCE && !codeChallenge) {
161161
throw OAuthException.invalidRequest(
162162
"code_challenge",
163163
"The authorization server requires public clients to use PKCE RFC-7636",
@@ -167,7 +167,7 @@ export class AuthCodeGrant extends AbstractAuthorizedGrant {
167167
if (codeChallenge) {
168168
const codeChallengeMethod = this.getQueryStringParameter("code_challenge_method", request, "plain");
169169

170-
if (!REGEXP_CODE_CHALLENGE.test(this.useUrlEncode ? base64decode(codeChallenge) : codeChallenge)) {
170+
if (!REGEXP_CODE_CHALLENGE.test(this.options.useUrlEncode ? base64decode(codeChallenge) : codeChallenge)) {
171171
throw OAuthException.invalidRequest(
172172
"code_challenge",
173173
`Code challenge must follow the specifications of RFC-7636 and match ${REGEXP_CODE_CHALLENGE.toString()}.`,

test/unit/grants/auth_code.grant.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ describe("authorization_code grant", () => {
173173
state: "state-is-a-secret",
174174
},
175175
});
176-
grant.requiresPKCE = false;
176+
grant.options.requiresPKCE = false;
177177

178178
// act
179179
const authorizationRequest = await grant.validateAuthorizationRequest(request);
@@ -320,7 +320,7 @@ describe("authorization_code grant", () => {
320320
// it("uses clients redirect url if request ", async () => {});
321321

322322
it("is successful without pkce flow", async () => {
323-
grant.requiresPKCE = false;
323+
grant.options.requiresPKCE = false;
324324
const authorizationRequest = new AuthorizationRequest("authorization_code", client, "http://example.com");
325325
authorizationRequest.isAuthorizationApproved = true;
326326
authorizationRequest.state = "abc123";
@@ -396,7 +396,7 @@ describe("authorization_code grant", () => {
396396
});
397397

398398
it("is successful without pkce", async () => {
399-
grant.requiresPKCE = false;
399+
grant.options.requiresPKCE = false;
400400
authorizationRequest = new AuthorizationRequest("authorization_code", client, "http://example.com");
401401
authorizationRequest.isAuthorizationApproved = true;
402402
authorizationRequest.user = user;

0 commit comments

Comments
 (0)