Skip to content
Draft
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
6 changes: 6 additions & 0 deletions examples/directory/clients/My Native app.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@
"token_endpoint_auth_method": "none",
"custom_login_page_on": true,
"oidc_logout": {
"backchannel_logout_urls": [
"https://example.com/logout"
],
"backchannel_logout_initiators": {
"mode": "custom",
"selected_initiators": [
"rp-logout",
"idp-logout"
]
},
"backchannel_logout_session_metadata": {
"include": true
}
}
}
10 changes: 10 additions & 0 deletions examples/yaml/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ clients:
native_social_login:
google:
enabled: true
oidc_logout:
backchannel_logout_urls:
- "https://example.com/logout"
backchannel_logout_initiators:
mode: "custom"
selected_initiators:
- "rp-logout"
- "idp-logout"
backchannel_logout_session_metadata:
include: true
# Add other client settings https://auth0.com/docs/api/management/v2#!/Clients/post_clients
-
name: "My Resource Server Client"
Expand Down
58 changes: 58 additions & 0 deletions src/tools/auth0/handlers/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,64 @@ export const schema = {
},
},
},
oidc_logout: {
type: ['object', 'null'],
description: 'Configuration for OIDC backchannel logout',
properties: {
backchannel_logout_urls: {
type: 'array',
description:
'Comma-separated list of URLs that are valid to call back from Auth0 for OIDC backchannel logout. Currently only one URL is allowed.',
items: {
type: 'string',
},
},
backchannel_logout_initiators: {
type: 'object',
description: 'Configuration for OIDC backchannel logout initiators',
properties: {
mode: {
type: 'string',
schemaName: 'ClientOIDCBackchannelLogoutInitiatorsModeEnum',
enum: ['custom', 'all'],
description:
'The `mode` property determines the configuration method for enabling initiators. `custom` enables only the initiators listed in the selected_initiators array, `all` enables all current and future initiators.',
},
selected_initiators: {
type: 'array',
items: {
type: 'string',
enum: [
'rp-logout',
'idp-logout',
'password-changed',
'session-expired',
'session-revoked',
'account-deleted',
'email-identifier-changed',
'mfa-phone-unenrolled',
'account-deactivated',
],
description:
'The `selected_initiators` property contains the list of initiators to be enabled for the given application.',
},
},
},
},
backchannel_logout_session_metadata: {
type: ['object', 'null'],
description:
'Controls whether session metadata is included in the logout token. Default value is null.',
properties: {
include: {
type: 'boolean',
description:
'The `include` property determines whether session metadata is included in the logout token.',
},
},
},
},
},
},
required: ['name'],
},
Expand Down
79 changes: 79 additions & 0 deletions test/context/directory/clients.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,83 @@ describe('#directory context clients', () => {
organization_require_behavior: 'no_prompt',
});
});

it('should process clients with oidc_logout', async () => {
const files = {
[constants.CLIENTS_DIRECTORY]: {
'oidcLogoutClient.json':
'{ "app_type": "regular_web", "name": "oidcLogoutClient", "oidc_logout": { "backchannel_logout_urls": ["https://example.com/logout"], "backchannel_logout_initiators": { "mode": "custom", "selected_initiators": ["rp-logout", "idp-logout"] }, "backchannel_logout_session_metadata": { "include": true } } }',
'simpleClient.json': '{ "app_type": "spa", "name": "simpleClient" }',
},
};

const repoDir = path.join(testDataDir, 'directory', 'clientsWithOidcLogout');
createDir(repoDir, files);

const config = {
AUTH0_INPUT_FILE: repoDir,
};
const context = new Context(config, mockMgmtClient());
await context.loadAssetsFromLocal();

const target = [
{
app_type: 'regular_web',
name: 'oidcLogoutClient',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
},
},
{ app_type: 'spa', name: 'simpleClient' },
];
expect(context.assets.clients).to.deep.equal(target);
});

it('should dump clients with oidc_logout', async () => {
const dir = path.join(testDataDir, 'directory', 'clientsOidcLogoutDump');
cleanThenMkdir(dir);
const context = new Context({ AUTH0_INPUT_FILE: dir }, mockMgmtClient());

context.assets.clients = [
{
name: 'oidcLogoutClient',
app_type: 'regular_web',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
},
},
];

await handler.dump(context);

const dumpedClient = loadJSON(path.join(dir, 'clients', 'oidcLogoutClient.json'));
expect(dumpedClient).to.deep.equal({
name: 'oidcLogoutClient',
app_type: 'regular_web',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
},
});
});
});
54 changes: 54 additions & 0 deletions test/context/yaml/clients.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,58 @@ describe('#YAML context clients', () => {

expect(context.assets.clients).to.deep.equal(target);
});

it('should process clients with oidc_logout', async () => {
const dir = path.join(testDataDir, 'yaml', 'clientsWithOidcLogout');
cleanThenMkdir(dir);

const yaml = `
clients:
-
name: "oidcLogoutClient"
app_type: "regular_web"
oidc_logout:
backchannel_logout_urls: ['https://example.com/logout']
backchannel_logout_initiators:
mode: 'custom'
selected_initiators: ['rp-logout', 'idp-logout']
backchannel_logout_session_metadata:
include: true
-
name: "simpleClient"
app_type: "spa"
`;

const target = [
{
name: 'oidcLogoutClient',
app_type: 'regular_web',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
},
},
{
name: 'simpleClient',
app_type: 'spa',
},
];

const yamlFile = path.join(dir, 'clients.yaml');
fs.writeFileSync(yamlFile, yaml);

const config = {
AUTH0_INPUT_FILE: yamlFile,
};
const context = new Context(config, mockMgmtClient());
await context.loadAssetsFromLocal();

expect(context.assets.clients).to.deep.equal(target);
});
});
117 changes: 117 additions & 0 deletions test/tools/auth0/handlers/clients.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1199,5 +1199,122 @@ describe('#clients handler', () => {
expect(newOnlyClient).to.not.have.property('cross_origin_auth');
expect(newOnlyClient.cross_origin_authentication).to.equal(false);
});

it('should create client with oidc_logout configuration', async () => {
const clientWithOidcLogout = {
name: 'My Client with OIDC Logout',
app_type: 'regular_web',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
},
};

const auth0 = {
clients: {
create: function (data) {
(() => expect(this).to.not.be.undefined)();
expect(data).to.be.an('object');
expect(data.name).to.equal('My Client with OIDC Logout');
expect(data.oidc_logout).to.deep.equal({
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout', 'idp-logout'],
},
backchannel_logout_session_metadata: {
include: true,
},
});
return Promise.resolve({ data });
},
update: () => Promise.resolve({ data: [] }),
delete: () => Promise.resolve({ data: [] }),
list: (params) => mockPagedData(params, 'clients', []),
},
connectionProfiles: { list: (params) => mockPagedData(params, 'connectionProfiles', []) },
userAttributeProfiles: {
list: (params) => mockPagedData(params, 'userAttributeProfiles', []),
},
pool,
};

const handler = new clients.default({ client: pageClient(auth0), config });
const stageFn = Object.getPrototypeOf(handler).processChanges;

await stageFn.apply(handler, [{ clients: [clientWithOidcLogout] }]);
});

it('should update client with oidc_logout configuration', async () => {
const auth0 = {
clients: {
create: () => Promise.resolve({ data: [] }),
update: function (client_id, data) {
(() => expect(this).to.not.be.undefined)();
expect(client_id).to.equal('client1');
expect(data.oidc_logout).to.deep.equal({
backchannel_logout_urls: ['https://new-example.com/logout'],
backchannel_logout_initiators: {
mode: 'all',
selected_initiators: [],
},
backchannel_logout_session_metadata: {
include: false,
},
});
return Promise.resolve({ data });
},
delete: () => Promise.resolve({ data: [] }),
list: (params) =>
mockPagedData(params, 'clients', [
{
client_id: 'client1',
name: 'My Client',
oidc_logout: {
backchannel_logout_urls: ['https://example.com/logout'],
backchannel_logout_initiators: {
mode: 'custom',
selected_initiators: ['rp-logout'],
},
},
},
]),
},
connectionProfiles: { list: (params) => mockPagedData(params, 'connectionProfiles', []) },
userAttributeProfiles: {
list: (params) => mockPagedData(params, 'userAttributeProfiles', []),
},
pool,
};

const handler = new clients.default({ client: pageClient(auth0), config });
const stageFn = Object.getPrototypeOf(handler).processChanges;

await stageFn.apply(handler, [
{
clients: [
{
name: 'My Client',
oidc_logout: {
backchannel_logout_urls: ['https://new-example.com/logout'],
backchannel_logout_initiators: {
mode: 'all',
selected_initiators: [],
},
backchannel_logout_session_metadata: {
include: false,
},
},
},
],
},
]);
});
});
});