Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add url_state to SignoutRequest #1818

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 7 additions & 3 deletions docs/oidc-client-ts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export type ExtraHeader = string | (() => string);
export type ExtraSigninRequestArgs = Pick<CreateSigninRequestArgs, "nonce" | "extraQueryParams" | "extraTokenParams" | "state" | "redirect_uri" | "prompt" | "acr_values" | "login_hint" | "scope" | "max_age" | "ui_locales" | "resource" | "url_state">;

// @public (undocumented)
export type ExtraSignoutRequestArgs = Pick<CreateSignoutRequestArgs, "extraQueryParams" | "state" | "id_token_hint" | "post_logout_redirect_uri">;
export type ExtraSignoutRequestArgs = Pick<CreateSignoutRequestArgs, "extraQueryParams" | "state" | "id_token_hint" | "post_logout_redirect_uri" | "url_state">;

// Warning: (ae-forgotten-export) The symbol "Mandatory" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -336,7 +336,7 @@ export class OidcClient {
// (undocumented)
createSigninRequest({ state, request, request_uri, request_type, id_token_hint, login_hint, skipUserInfo, nonce, url_state, response_type, scope, redirect_uri, prompt, display, max_age, ui_locales, acr_values, resource, response_mode, extraQueryParams, extraTokenParams, dpopJkt, omitScopeWhenRequesting, }: CreateSigninRequestArgs): Promise<SigninRequest>;
// (undocumented)
createSignoutRequest({ state, id_token_hint, client_id, request_type, post_logout_redirect_uri, extraQueryParams, }?: CreateSignoutRequestArgs): Promise<SignoutRequest>;
createSignoutRequest({ state, id_token_hint, client_id, request_type, url_state, post_logout_redirect_uri, extraQueryParams, }?: CreateSignoutRequestArgs): Promise<SignoutRequest>;
// (undocumented)
getDpopProof(dpopStore: DPoPStore, nonce?: string): Promise<string>;
// (undocumented)
Expand Down Expand Up @@ -855,7 +855,7 @@ export type SignoutRedirectArgs = RedirectParams & ExtraSignoutRequestArgs;

// @public (undocumented)
export class SignoutRequest {
constructor({ url, state_data, id_token_hint, post_logout_redirect_uri, extraQueryParams, request_type, client_id, }: SignoutRequestArgs);
constructor({ url, state_data, id_token_hint, post_logout_redirect_uri, extraQueryParams, request_type, client_id, url_state, }: SignoutRequestArgs);
// (undocumented)
readonly state?: State;
// (undocumented)
Expand All @@ -877,6 +877,8 @@ export interface SignoutRequestArgs {
state_data?: unknown;
// (undocumented)
url: string;
// (undocumented)
url_state?: string;
}

// @public (undocumented)
Expand All @@ -890,6 +892,8 @@ export class SignoutResponse {
error_uri: string | null;
// (undocumented)
readonly state: string | null;
// (undocumented)
url_state?: string;
userState: unknown;
}

Expand Down
4 changes: 3 additions & 1 deletion src/OidcClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import { CryptoUtils, JwtUtils } from "./utils";
import { CryptoUtils, JwtUtils, URL_STATE_DELIMITER } from "./utils";
import type { ErrorResponse } from "./errors";
import type { JwtClaims } from "./Claims";
import { OidcClient } from "./OidcClient";
Expand Down Expand Up @@ -838,6 +838,7 @@ describe("OidcClient", () => {
state: "foo",
post_logout_redirect_uri: "bar",
id_token_hint: "baz",
url_state: "qux",
});

// assert
Expand All @@ -847,6 +848,7 @@ describe("OidcClient", () => {
expect(url).toContain("http://sts/signout");
expect(url).toContain("post_logout_redirect_uri=bar");
expect(url).toContain("id_token_hint=baz");
expect(url).toContain(encodeURIComponent(URL_STATE_DELIMITER + "qux"));
});

it("should pass params to SignoutRequest w/o id_token_hint and client_id", async () => {
Expand Down
2 changes: 2 additions & 0 deletions src/OidcClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ export class OidcClient {
id_token_hint,
client_id,
request_type,
url_state,
post_logout_redirect_uri = this.settings.post_logout_redirect_uri,
extraQueryParams = this.settings.extraQueryParams,
}: CreateSignoutRequestArgs = {}): Promise<SignoutRequest> {
Expand Down Expand Up @@ -358,6 +359,7 @@ export class OidcClient {
state_data: state,
extraQueryParams,
request_type,
url_state,
});

// house cleaning
Expand Down
37 changes: 37 additions & 0 deletions src/SignoutRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import { SignoutRequest, type SignoutRequestArgs } from "./SignoutRequest";
import { URL_STATE_DELIMITER } from "./utils";

describe("SignoutRequest", () => {

Expand Down Expand Up @@ -70,6 +71,19 @@ describe("SignoutRequest", () => {
expect(subject.url).toContain("state=" + subject.state!.id);
});

it("should include state if post_logout_redirect_uri and url_state provided even if state_data is empty", () => {
// arrange
delete settings.state_data;
settings.url_state = "foo";

// act
subject = new SignoutRequest(settings);

// assert
expect(subject.state).toBeDefined();
expect(subject.url).toContain("state=" + subject.state!.id + encodeURIComponent(URL_STATE_DELIMITER + "foo"));
});

it("should not include state if no post_logout_redirect_uri provided", () => {
// arrange
delete settings.post_logout_redirect_uri;
Expand All @@ -80,6 +94,29 @@ describe("SignoutRequest", () => {
// assert
expect(subject.url).not.toContain("state=");
});

it("should include url_state", async () => {
// arrange
settings.url_state = "foo";

// act
subject = new SignoutRequest(settings);

// assert
expect(subject.url).toContain("state=" + subject.state!.id + encodeURIComponent(URL_STATE_DELIMITER + "foo"));
});

it("should not include state or url_state if no post_logout_redirect_uri provided", () => {
// arrange
delete settings.post_logout_redirect_uri;
settings.url_state = "foo";

// act
subject = new SignoutRequest(settings);

// assert
expect(subject.url).not.toContain("state=");
});

it("should include id_token_hint, post_logout_redirect_uri, and state", () => {
// assert
Expand Down
16 changes: 11 additions & 5 deletions src/SignoutRequest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import { Logger } from "./utils";
import { Logger, URL_STATE_DELIMITER } from "./utils";
import { State } from "./State";

/**
Expand All @@ -22,6 +22,7 @@ export interface SignoutRequestArgs {
request_type?: string;
/** custom "state", which can be used by a caller to have "data" round tripped */
state_data?: unknown;
url_state?: string;
}

/**
Expand All @@ -35,7 +36,7 @@ export class SignoutRequest {

public constructor({
url,
state_data, id_token_hint, post_logout_redirect_uri, extraQueryParams, request_type, client_id,
state_data, id_token_hint, post_logout_redirect_uri, extraQueryParams, request_type, client_id, url_state,
}: SignoutRequestArgs) {
if (!url) {
this._logger.error("ctor: No url passed");
Expand All @@ -53,10 +54,15 @@ export class SignoutRequest {
if (post_logout_redirect_uri) {
parsedUrl.searchParams.append("post_logout_redirect_uri", post_logout_redirect_uri);

if (state_data) {
this.state = new State({ data: state_data, request_type });
// Add state if either data needs to be stored, or url_state set for an intermediate proxy
if (state_data || url_state) {
this.state = new State({ data: state_data, request_type, url_state });

parsedUrl.searchParams.append("state", this.state.id);
let stateParam = this.state.id;
if (url_state) {
stateParam = `${stateParam}${URL_STATE_DELIMITER}${url_state}`;
}
parsedUrl.searchParams.append("state", stateParam);
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/SignoutResponse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,14 @@ describe("SignoutResponse", () => {
// assert
expect(subject.state).toEqual("foo");
});

it("should read url_state", () => {
// act
const subject = new SignoutResponse(new URLSearchParams("state=foo;bar"));

// assert
expect(subject.state).toEqual("foo");
expect(subject.url_state).toEqual("bar");
});
});
});
10 changes: 10 additions & 0 deletions src/SignoutResponse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import { URL_STATE_DELIMITER } from "./utils";

/**
* @public
* @see https://openid.net/specs/openid-connect-core-1_0.html#AuthError
Expand All @@ -18,9 +20,17 @@ export class SignoutResponse {

/** custom state data set during the initial signin request */
public userState: unknown;
public url_state?: string;

public constructor(params: URLSearchParams) {
this.state = params.get("state");
if (this.state) {
const splitState = decodeURIComponent(this.state).split(URL_STATE_DELIMITER);
this.state = splitState[0];
if (splitState.length > 1) {
this.url_state = splitState.slice(1).join(URL_STATE_DELIMITER);
}
}

this.error = params.get("error");
this.error_description = params.get("error_description");
Expand Down
1 change: 1 addition & 0 deletions src/UserManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ describe("UserManager", () => {
extraQueryParams: { q : "q" },
state: "state",
post_logout_redirect_uri: "http://app/extra_callback",
url_state: "foo",
};

// act
Expand Down
2 changes: 1 addition & 1 deletion src/UserManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type ExtraSigninRequestArgs = Pick<CreateSigninRequestArgs, "nonce" | "ex
/**
* @public
*/
export type ExtraSignoutRequestArgs = Pick<CreateSignoutRequestArgs, "extraQueryParams" | "state" | "id_token_hint" | "post_logout_redirect_uri">;
export type ExtraSignoutRequestArgs = Pick<CreateSignoutRequestArgs, "extraQueryParams" | "state" | "id_token_hint" | "post_logout_redirect_uri" | "url_state">;

/**
* @public
Expand Down