Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/types/react-18.2.39
Browse files Browse the repository at this point in the history
  • Loading branch information
Widcket authored Dec 4, 2023
2 parents 8311860 + 1225b65 commit 97e4c02
Show file tree
Hide file tree
Showing 38 changed files with 1,050 additions and 109 deletions.
75 changes: 62 additions & 13 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Add a signup handler](#add-a-signup-handler)
- [Use with Base Path and Internationalized Routing](#use-with-base-path-and-internationalized-routing)
- [Use a custom session store](#use-a-custom-session-store)
- [Back-Channel Logout](#back-channel-logout)

See also the [example app](./example-app).

Expand Down Expand Up @@ -505,25 +506,73 @@ class Store implements SessionStore {
}
}

let auth0;

export default () => {
if (!auth0) {
auth0 = initAuth0({
session: {
store: new Store()
}
});
export default initAuth0({
session: {
store: new Store()
}
return auth0;
};
});
```

Then use your instance wherever you use the server methods of the SDK.

```ts
// /pages/api/auth/[auth0].js
import getAuth0 from '../../../lib/auth0';
import auth0 from '../../../lib/auth0';

export default auth0.handleAuth();
```

### Back-Channel Logout

Back-Channel Logout requires a session store, so you'll need to create your own instance of the SDK in code and pass an instance of your session store to the SDK's configuration:

```js
// lib/auth0.ts
import { initAuth0 } from '@auth0/nextjs-auth0';

export default getAuth0().handleAuth();
export default initAuth0({
backChannelLogout: {
store: new Store() // See "Use a custom session store" for how to define a Store class.
}
});
```

If you are already using a session store, you can just reuse that:

```js
// lib/auth0.ts
import { initAuth0 } from '@auth0/nextjs-auth0';

export default initAuth0({
session: {
store: new Store()
},
backchannelLogout: true
});
```

Once you've enabled the `backchannelLogout` option, `handleAuth` will create a `/api/auth/backchannel-logout` POST handler.

#### Pages Router

```ts
// /pages/api/auth/[auth0].js
import auth0 from '../../../lib/auth0';

export default auth0.handleAuth();
```

#### App Router

```ts
// /app/api/auth/[auth0]/route.js
import auth0 from '../../../lib/auth0';

const handler = auth0.handleAuth();

// For Back-Channel Logout you need to export a GET and a POST handler.
export { handler as GET, handler as POST };
```

Then configure your tenant following [these instructions](https://auth0.com/docs/authenticate/login/logout/back-channel-logout/configure-back-channel-logout#configure-auth0).
Your "OpenID Connect Back-Channel Logout URI" will be `{YOUR_AUTH0_BASE_URL}/api/auth/backchannel-logout`.
1 change: 1 addition & 0 deletions jest-edge.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'**/tests/handlers/logout.test.ts',
'**/tests/handlers/callback.test.ts',
'**/tests/handlers/profile.test.ts',
'**/tests/handlers/backchannel-logout.test.ts',
'**/tests/http/auth0-next-request.test.ts',
'**/tests/http/auth0-next-response.test.ts',
'**/tests/helpers/with-middleware-auth-required.test.ts',
Expand Down
60 changes: 39 additions & 21 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"@testing-library/react-hooks": "^5.0.3",
"@types/body-parser": "^1.19.0",
"@types/clone": "^2.1.0",
"@types/cookie": "^0.5.1",
"@types/cookie": "^0.6.0",
"@types/debug": "^4.1.5",
"@types/jest": "^29.5.2",
"@types/jsonwebtoken": "^8.5.0",
Expand Down
6 changes: 6 additions & 0 deletions src/auth0-session/client/abstract-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export interface AuthorizationParameters {
[key: string]: unknown;
}

export type IssuerMetadata = {
issuer: string;
jwks_uri?: string;
};

export abstract class AbstractClient {
constructor(protected config: Config, protected telemetry: Telemetry) {}
abstract authorizationUrl(parameters: Record<string, unknown>): Promise<string>;
Expand All @@ -103,6 +108,7 @@ export abstract class AbstractClient {
abstract generateRandomCodeVerifier(): string;
abstract generateRandomNonce(): string;
abstract calculateCodeChallenge(codeVerifier: string): Promise<string> | string;
abstract getIssuerMetadata(): Promise<IssuerMetadata>;
}

export type GetClient = (config: Config) => Promise<AbstractClient>;
5 changes: 5 additions & 0 deletions src/auth0-session/client/edge-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ export class EdgeClient extends AbstractClient {
calculateCodeChallenge(codeVerifier: string): Promise<string> {
return oauth.calculatePKCECodeChallenge(codeVerifier);
}

async getIssuerMetadata(): Promise<oauth.AuthorizationServer> {
const [as] = await this.getClient();
return as;
}
}

export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<EdgeClient>) => {
Expand Down
8 changes: 7 additions & 1 deletion src/auth0-session/client/node-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
EndSessionParameters,
errors,
generators,
Issuer
Issuer,
IssuerMetadata
} from 'openid-client';
import { ApplicationError, DiscoveryError, EscapedError, IdentityProviderError, UserInfoError } from '../utils/errors';
import { createPrivateKey } from 'crypto';
Expand Down Expand Up @@ -236,6 +237,11 @@ export class NodeClient extends AbstractClient {
calculateCodeChallenge(codeVerifier: string): string {
return generators.codeChallenge(codeVerifier);
}

async getIssuerMetadata(): Promise<IssuerMetadata> {
const { issuer } = await this.getClient();
return issuer.metadata;
}
}

export const clientGetter = (telemetry: Telemetry): ((config: Config) => Promise<NodeClient>) => {
Expand Down
24 changes: 24 additions & 0 deletions src/auth0-session/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,30 @@ export interface Config {
* cookie (Setting SameSite=Strict for example).
*/
transactionCookie: Omit<CookieConfig, 'transient' | 'httpOnly'> & { name: string };

/**
* Set to `true` to enable Back-Channel Logout in your application.
*
* On receipt of a Logout Token the backchannelLogout webhook will store the token, then on any
* subsequent requests, will check the store for a Logout Token that corresponds to the
* current session. If it finds one, it will log the user out.
*
* In order for this to work you need to specify a {@link BackchannelLogoutOptions.store},
* or you can reuse {@link SessionConfigParams.store} if you are using one already.
*
* See: https://openid.net/specs/openid-connect-backchannel-1_0.html
*/
backchannelLogout: boolean | BackchannelLogoutOptions;
}

export interface BackchannelLogoutOptions {
/**
* Used to store Back-Channel Logout entries, you can specify a separate store
* for this or just reuse {@link SessionConfig.store} if you are using one already.
*
* The store should have `get`, `set` and `destroy` methods.
*/
store: SessionStore<any>;
}

/**
Expand Down
21 changes: 19 additions & 2 deletions src/auth0-session/get-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ const paramsSchema = Joi.object({
.default(7 * 24 * 60 * 60), // 7 days,
autoSave: Joi.boolean().optional().default(true),
name: Joi.string().token().optional().default('appSession'),
store: Joi.object().optional(),
store: Joi.object()
.optional()
.when(Joi.ref('/backchannelLogout'), {
not: false,
then: Joi.when('/backchannelLogout.store', {
not: Joi.exist(),
then: Joi.object().required().messages({
// eslint-disable-next-line max-len
'any.required': `Back-Channel Logout requires a "backchannelLogout.store" (you can also reuse "session.store" if you have stateful sessions).`
})
})
}),
genId: Joi.function().maxArity(2).when(Joi.ref('store'), { then: Joi.required() }),
storeIDToken: Joi.boolean().optional().default(true),
cookie: Joi.object({
Expand Down Expand Up @@ -176,7 +187,13 @@ const paramsSchema = Joi.object({
path: Joi.string().uri({ relativeOnly: true }).default(Joi.ref('/session.cookie.transient'))
})
.default()
.unknown(false)
.unknown(false),
backchannelLogout: Joi.alternatives([
Joi.object({
store: Joi.object().optional()
}),
Joi.boolean()
]).default(false)
});

export type DeepPartial<T> = {
Expand Down
Loading

0 comments on commit 97e4c02

Please sign in to comment.