Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- [Transaction Cookie Configuration](#transaction-cookie-configuration)
- [Database sessions](#database-sessions)
- [Back-Channel Authentication](#back-channel-authentication)
- [Connected Accounts](#connected-accounts)
- [Back-Channel Logout](#back-channel-logout)
- [Combining middleware](#combining-middleware)
- [ID Token claims and the user object](#id-token-claims-and-the-user-object)
Expand Down Expand Up @@ -1331,6 +1332,84 @@ const tokenResponse = await auth0.getTokenByBackchannelAuth({
> Using Client-Initiated Backchannel Authentication requires the feature to be enabled in the Auth0 dashboard.
> Read [the Auth0 docs](https://auth0.com/docs/get-started/authentication-and-authorization-flow/client-initiated-backchannel-authentication-flow) to learn more about Client-Initiated Backchannel Authentication.

## Connected Accounts

The SDK can be configured to mount an endpoint to facilitate the connected accounts flow. To mount this route, set the `enableConnectAccountEndpoint` option to `true` when instantiating the Auth0 client, like so:

```ts
// ./lib/auth0.ts
import { Auth0Client } from "@auth0/nextjs-auth0/server";

export const auth0 = new Auth0Client({
enableConnectAccountEndpoint: true
});
```

By default, the route will be mounted at `/auth/connect`. You can customize this path by specifying a `routes.connectAccount` option, like so:

```ts
// ./lib/auth0.ts
import { Auth0Client } from "@auth0/nextjs-auth0/server";

export const auth0 = new Auth0Client({
enableConnectAccountEndpoint: true,
routes: {
connectAccount: "/auth/connect"
}
});
```

The connect endpoint (`/auth/connect` or your custom path) accepts the following query parameters:

- `connection`: (required) the name of the connection to use for linking the account
- `returnTo`: (optional) the URL to redirect the user to after they have completed the connection flow.
- Any additional parameters will be passed as the `authorizationParams` in the call to `/me/v1/connected-accounts/connect`.

### `onCallback` hook

When a user is redirected back to your application after completing the connected accounts flow, the `onCallback` hook will be called. You can use this hook to run custom logic after the user has connected their account, like so:

```ts
import { NextResponse } from "next/server";
import { Auth0Client } from "@auth0/nextjs-auth0/server";

export const auth0 = new Auth0Client({
async onCallback(err, ctx, session) {
// `ctx` will contain the following properties when handling a connected account callback:
// - `connectedAccount`: the connected account object (`CompleteConnectAccountResponse`) if the connection was successful
// - `responseType`: will be set to `connect_code` when handling a connected accounts callback (`RESPONSE_TYPES.ConnectCode`)
// - `returnTo`: the returnTo URL specified when calling the connect endpoint (if any)

return NextResponse.redirect(
new URL(ctx.returnTo ?? "/", process.env.APP_BASE_URL)
);
},
enableConnectAccountEndpoint: true
});
```

### `connectAccount` method

In case you'd like to have more control over the connected accounts flow, a `connectAccount` method is also available on the Auth0 client instance. For example, you could mount a custom route to start the connected accounts flow, like so:

```ts
import { auth0 } from "@/lib/auth0";

export async function GET() {
const res = await auth0.connectAccount({
connection: "my-connection",
authorizationParams: {
scope: "openid profile offline_access read:something",
prompt: "consent",
audience: "https://myapi.com"
},
returnTo: "/connected"
});

return res;
}
```

## Back-Channel Logout

The SDK can be configured to listen to [Back-Channel Logout](https://auth0.com/docs/authenticate/login/logout/back-channel-logout) events. By default, a route will be mounted `/auth/backchannel-logout` which will verify the logout token and call the `deleteByLogoutToken` method of your session store implementation to allow you to remove the session.
Expand Down
112 changes: 107 additions & 5 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class OAuth2Error extends SdkError {
constructor({ code, message }: { code: string; message?: string }) {
super(
message ??
"An error occured while interacting with the authorization server."
"An error occurred while interacting with the authorization server."
);
this.name = "OAuth2Error";
this.code = code;
Expand Down Expand Up @@ -60,7 +60,7 @@ export class AuthorizationError extends SdkError {
public cause: OAuth2Error;

constructor({ cause, message }: { cause: OAuth2Error; message?: string }) {
super(message ?? "An error occured during the authorization flow.");
super(message ?? "An error occurred during the authorization flow.");
this.cause = cause;
this.name = "AuthorizationError";
}
Expand All @@ -72,7 +72,7 @@ export class AuthorizationCodeGrantRequestError extends SdkError {
constructor(message?: string) {
super(
message ??
"An error occured while preparing or performing the authorization code grant request."
"An error occurred while preparing or performing the authorization code grant request."
);
this.name = "AuthorizationCodeGrantRequestError";
}
Expand All @@ -85,7 +85,7 @@ export class AuthorizationCodeGrantError extends SdkError {
constructor({ cause, message }: { cause: OAuth2Error; message?: string }) {
super(
message ??
"An error occured while trying to exchange the authorization code."
"An error occurred while trying to exchange the authorization code."
);
this.cause = cause;
this.name = "AuthorizationCodeGrantError";
Expand All @@ -98,7 +98,7 @@ export class BackchannelLogoutError extends SdkError {
constructor(message?: string) {
super(
message ??
"An error occured while completing the backchannel logout request."
"An error occurred while completing the backchannel logout request."
);
this.name = "BackchannelLogoutError";
}
Expand Down Expand Up @@ -191,3 +191,105 @@ export class AccessTokenForConnectionError extends SdkError {
this.cause = cause;
}
}

/**
* Error class representing a connect account request error.
*/
export class MyAccountApiError extends SdkError {
public name: string = "MyAccountApiError";
public code: string = "my_account_api_error";
public type: string;
public title: string;
public detail: string;
public status: number;
public validationErrors?: Array<{
/**
* A human-readable description of the specific error. Required.
*/
detail: string;
/**
* The name of the invalid parameter. Optional.
*/
field?: string;
/**
* A JSON Pointer that points to the exact location of the error in a JSON document being validated. Optional.
*/
pointer?: string;
/**
* Specifies the source of the error (e.g., body, query, or header in an HTML message). Optional.
*/
source?: string;
}>;

constructor({
type,
title,
detail,
status,
validationErrors
}: {
type: string;
title: string;
detail: string;
status: number;
validationErrors?: Array<{
detail: string;
field?: string;
pointer?: string;
source?: string;
}>;
}) {
super(`${title}: ${detail}`);
this.type = type;
this.title = title;
this.detail = detail;
this.status = status;
this.validationErrors = validationErrors;
}
}

/**
* Enum representing error codes related to the connect account flow.
*/
export enum ConnectAccountErrorCodes {
/**
* The session is missing.
*/
MISSING_SESSION = "missing_session",

/**
* Failed to initiate the connect account flow.
*/
FAILED_TO_INITIATE = "failed_to_initiate",

/**
* Failed to complete the connect account flow.
*/
FAILED_TO_COMPLETE = "failed_to_complete"
}

/**
* Error class representing a connect account error.
*/
export class ConnectAccountError extends SdkError {
/**
* The error code associated with the connect account error.
*/
public code: string;
public cause?: MyAccountApiError;

constructor({
code,
message,
cause
}: {
code: string;
message: string;
cause?: MyAccountApiError;
}) {
super(message);
this.name = "ConnectAccountError";
this.code = code;
this.cause = cause;
}
}
Loading