Skip to content

Commit 6738d23

Browse files
committed
PKCE + Token Refresh for code flow
1 parent 9a9def6 commit 6738d23

14 files changed

+306
-102
lines changed

.vscode/settings.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@
2525
"typescriptreact",
2626
"yml"
2727
],
28-
"spellright.language": "de",
28+
"spellright.language": [
29+
"en"
30+
],
2931
"spellright.documentTypes": [
3032
"latex",
31-
"plaintext"
33+
"plaintext",
34+
"markdown"
3235
]
3336
}

README.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,17 @@ Successfully tested with **Angular 7** and its Router, PathLocationStrategy as w
3838
- Feel free to file pull requests
3939
- The closed issues contain some ideas for PRs and enhancements (see labels)
4040
- If you want to contribute to the docs, you can do so in the `docs-src` folder. Make sure you update `summary.json` as well. Then generate the docs with the following commands:
41-
```
42-
npm install -g @compodoc/compodoc
43-
npm run docs
44-
```
41+
42+
```
43+
npm install -g @compodoc/compodoc
44+
npm run docs
45+
```
4546

4647
# Features
47-
- Logging in via OAuth2 and OpenId Connect (OIDC) Implicit Flow (where a user is redirected to Identity Provider)
48+
- Logging in via Implicit Flow (where a user is redirected to Identity Provider)
49+
- Logging in via Code Flow + PKCE
4850
- "Logging in" via Password Flow (where a user enters their password into the client)
49-
- Token Refresh for Password Flow by using a Refresh Token
51+
- Token Refresh for all supported flows
5052
- Automatically refreshing a token when/some time before it expires
5153
- Querying Userinfo Endpoint
5254
- Querying Discovery Document to ease configuration
@@ -130,7 +132,7 @@ export const authConfig: AuthConfig = {
130132
}
131133
```
132134

133-
Configure the OAuthService with this config object when the application starts up:
135+
Configure the ``OAuthService`` with this config object when the application starts up:
134136

135137
```TypeScript
136138
import { OAuthService } from 'angular-oauth2-oidc';
@@ -145,10 +147,10 @@ import { Component } from '@angular/core';
145147
export class AppComponent {
146148

147149
constructor(private oauthService: OAuthService) {
148-
this.configureWithNewConfigApi();
150+
this.configure();
149151
}
150152

151-
private configureWithNewConfigApi() {
153+
private configure() {
152154
this.oauthService.configure(authConfig);
153155
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
154156
this.oauthService.loadDiscoveryDocumentAndTryLogin();
@@ -173,7 +175,7 @@ export class HomeComponent {
173175
}
174176

175177
public login() {
176-
this.oauthService.initImplicitFlow();
178+
this.oauthService.initLoginFlow();
177179
}
178180

179181
public logoff() {
@@ -237,7 +239,7 @@ If you need more versatility, you can look in the [documentation](https://manfre
237239

238240
If you use the ``PathLocationStrategy`` (which is on by default) and have a general catch-all-route (``path: '**'``) you should be fine. Otherwise look up the section ``Routing with the HashStrategy`` in the [documentation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/).
239241

240-
## More Documentation
242+
## More Documentation (!)
241243

242244
See the [documentation](https://manfredsteyer.github.io/angular-oauth2-oidc/docs/) for more information about this library.
243245

docs-src/code-flow.md

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Code Flow
2+
3+
Since Version 8, this library also supports code flow and [PKCE](https://tools.ietf.org/html/rfc7636) to align with the current draft of the [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13) document.
4+
5+
6+
To configure your solution for code flow + PKCE you have to set the ``responseType`` to ``code``:
7+
8+
```typescript
9+
import { AuthConfig } from 'angular-oauth2-oidc';
10+
11+
export const authCodeFlowConfig: AuthConfig = {
12+
// Url of the Identity Provider
13+
issuer: 'https://demo.identityserver.io',
14+
15+
// URL of the SPA to redirect the user to after login
16+
redirectUri: window.location.origin + '/index.html',
17+
18+
// The SPA's id. The SPA is registerd with this id at the auth-server
19+
// clientId: 'server.code',
20+
clientId: 'spa',
21+
22+
// Just needed if your auth server demands a secret. In general, this
23+
// is a sign that the auth server is not configured with SPAs in mind
24+
// and it might not enforce further best practices vital for security
25+
// such applications.
26+
// dummyClientSecret: 'secret',
27+
28+
responseType: 'code',
29+
30+
// set the scope for the permissions the client should request
31+
// The first four are defined by OIDC.
32+
// Important: Request offline_access to get a refresh token
33+
// The api scope is a usecase specific one
34+
scope: 'openid profile email offline_access api',
35+
36+
showDebugInformation: true,
37+
38+
// Not recommented:
39+
// disablePKCI: true,
40+
};
41+
```
42+
43+
After this, you can initialize the code flow using:
44+
45+
```typescript
46+
this.oauthService.initCodeFlow();
47+
```
48+
49+
There is also a convenience method ``initLoginFlow`` which initializes either the code flow or the implicit flow depending on your configuration.
50+
51+
```typescript
52+
this.oauthService.initLoginFlow();
53+
```
54+
55+
Also -- as shown in the readme -- you have to execute the following code when bootstrapping to make the library to fetch the token:
56+
57+
```typescript
58+
this.oauthService.configure(authCodeFlowConfig);
59+
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
60+
this.oauthService.loadDiscoveryDocumentAndTryLogin();
61+
```
62+
63+
64+

docs-src/silent-refresh.md

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
1-
# Refreshing a Token when using Implicit Flow (Silent Refresh)
1+
# Refreshing a Token
22

3-
To refresh your tokens when using implicit flow you can use a silent refresh. This is a well-known solution that compensates the fact that implicit flow does not allow for issuing a refresh token. It uses a hidden iframe to get another token from the auth-server. When the user is there still logged in (by using a cookie) it will respond without user interaction and provide new tokens.
3+
The strategy to use for refreshing your token differs between implicit flow and code flow. Hence, you find here one separate section for both of them.
4+
5+
The last section shows how to automate refreshing for both flows.
6+
7+
## Refreshing when using Code Flow (not Implicit Flow!)
8+
9+
>> For refreshing a token with implicit flow, please see section below!
10+
11+
When using code flow, you can get an ``refresh_token``. While the original standard DOES NOT allow this for SPAs, the mentioned document proposes to ease this limitation. However, it specifies a list of requirements one should take care about before using refresh_tokens. Please make sure you respect those requirements.
12+
13+
Please also note, that you have to request the ``offline_access`` scope to get an refresh token.
14+
15+
To refresh your token, just call the ``refresh`` method:
16+
17+
```typescript
18+
this.oauthService.refresh();
19+
```
20+
21+
22+
## Refreshing when using Implicit Flow (not Code Flow!)
23+
24+
To refresh your tokens when using implicit flow you can use a silent refresh. This is a well-known solution that compensates the fact that implicit flow does not allow for issuing a refresh token. It uses a hidden iframe to get another token from the auth server. When the user is there still logged in (by using a cookie) it will respond without user interaction and provide new tokens.
425

526
To use this approach, setup a redirect uri for the silent refresh.
627

@@ -72,7 +93,8 @@ this
7293

7394
When there is an error in the iframe that prevents the communication with the main application, silentRefresh will give you a timeout. To configure the timespan for this, you can set the property ``silentRefreshTimeout`` (msec). The default value is 20.000 (20 seconds).
7495

75-
### Automatically refreshing a token when/ before it expires
96+
### Automatically refreshing a token when/ before it expires (Code Flow and Implicit Flow)
97+
7698

7799
To automatically refresh a token when/ some time before it expires, just call the following method after configuring the OAuthService:
78100

docs-src/summary.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
"file": "preserving-state.md"
99
},
1010
{
11-
"title": "Refreshing a Token (Silent Refresh)",
11+
"title": "Code Flow + PCKE",
12+
"file": "code-flow.md"
13+
},
14+
{
15+
"title": "Refreshing a Token",
1216
"file": "silent-refresh.md"
1317
},
1418
{

projects/lib/src/angular-oauth-oidic.module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { DefaultOAuthInterceptor } from './interceptors/default-oauth.intercepto
1515
import { ValidationHandler } from './token-validation/validation-handler';
1616
import { NullValidationHandler } from './token-validation/null-validation-handler';
1717
import { createDefaultLogger, createDefaultStorage } from './factories';
18+
import { CryptoHandler } from './token-validation/crypto-handler';
19+
import { JwksValidationHandler } from './token-validation/jwks-validation-handler';
1820

1921
@NgModule({
2022
imports: [CommonModule],
@@ -34,6 +36,7 @@ export class OAuthModule {
3436
{ provide: OAuthLogger, useFactory: createDefaultLogger },
3537
{ provide: OAuthStorage, useFactory: createDefaultStorage },
3638
{ provide: ValidationHandler, useClass: validationHandlerClass},
39+
{ provide: CryptoHandler, useClass: JwksValidationHandler },
3740
{
3841
provide: OAuthResourceServerErrorHandler,
3942
useClass: OAuthNoopResourceServerErrorHandler

projects/lib/src/auth.config.ts

+7
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ export class AuthConfig {
221221
*/
222222
public clockSkewInSec?: 600;
223223

224+
/**
225+
* Code Flow is by defauld used together with PKCI which is also higly recommented.
226+
* You can disbale it here by setting this flag to true.
227+
* https://tools.ietf.org/html/rfc7636#section-1.1
228+
*/
229+
public disablePKCE? = false;
230+
224231
constructor(json?: Partial<AuthConfig>) {
225232
if (json) {
226233
Object.assign(this, json);

projects/lib/src/base64-helper.ts

+8
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,11 @@ export function b64DecodeUnicode(str) {
1111
.join('')
1212
);
1313
}
14+
15+
export function base64UrlEncode(str): string {
16+
const base64 = btoa(str);
17+
return base64
18+
.replace(/\+/g, '-')
19+
.replace(/\//g, '_')
20+
.replace(/=/g, '');
21+
}

0 commit comments

Comments
 (0)