Skip to content

Fix: Pre-registered Client ID as fallback to DCR #684

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

Merged
merged 7 commits into from
Aug 13, 2025
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
97 changes: 97 additions & 0 deletions client/src/components/__tests__/AuthDebugger.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,103 @@ describe("AuthDebugger", () => {
});
});

describe("Client Registration behavior", () => {
it("uses preregistered (static) client information without calling DCR", async () => {
const preregClientInfo = {
client_id: "static_client_id",
client_secret: "static_client_secret",
redirect_uris: ["http://localhost:3000/oauth/callback/debug"],
};

// Return preregistered client info for the server-specific key
sessionStorageMock.getItem.mockImplementation((key) => {
if (
key ===
`[${defaultProps.serverUrl}] ${SESSION_KEYS.PREREGISTERED_CLIENT_INFORMATION}`
) {
return JSON.stringify(preregClientInfo);
}
return null;
});

const updateAuthState = jest.fn();

await act(async () => {
renderAuthDebugger({
updateAuthState,
authState: {
...defaultAuthState,
isInitiatingAuth: false,
oauthStep: "client_registration",
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
},
});
});

// Proceed from client_registration → authorization_redirect
await act(async () => {
fireEvent.click(screen.getByText("Continue"));
});

// Should NOT attempt dynamic client registration
expect(mockRegisterClient).not.toHaveBeenCalled();

// Should advance with the preregistered client info
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
oauthClientInfo: expect.objectContaining({
client_id: "static_client_id",
}),
oauthStep: "authorization_redirect",
}),
);
});

it("falls back to DCR when no static client information is available", async () => {
// No preregistered or dynamic client info present in session storage
sessionStorageMock.getItem.mockImplementation(() => null);

// DCR returns a new client
mockRegisterClient.mockResolvedValueOnce(mockOAuthClientInfo);

const updateAuthState = jest.fn();

await act(async () => {
renderAuthDebugger({
updateAuthState,
authState: {
...defaultAuthState,
isInitiatingAuth: false,
oauthStep: "client_registration",
oauthMetadata: mockOAuthMetadata as unknown as OAuthMetadata,
},
});
});

await act(async () => {
fireEvent.click(screen.getByText("Continue"));
});

expect(mockRegisterClient).toHaveBeenCalledTimes(1);

// Should save and advance with the DCR client info
expect(updateAuthState).toHaveBeenCalledWith(
expect.objectContaining({
oauthClientInfo: expect.objectContaining({
client_id: "test_client_id",
}),
oauthStep: "authorization_redirect",
}),
);

// Verify the dynamically registered client info was persisted
expect(sessionStorage.setItem).toHaveBeenCalledWith(
`[${defaultProps.serverUrl}] ${SESSION_KEYS.CLIENT_INFORMATION}`,
expect.any(String),
);
});
});

describe("OAuth State Persistence", () => {
it("should store auth state to sessionStorage before redirect in Quick OAuth Flow", async () => {
const updateAuthState = jest.fn();
Expand Down
14 changes: 9 additions & 5 deletions client/src/lib/oauth-state-machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,16 @@ export const oauthTransitions: Record<OAuthStep, StateTransition> = {
clientMetadata.scope = scopesSupported.join(" ");
}

const fullInformation = await registerClient(context.serverUrl, {
metadata,
clientMetadata,
});
// Try Static client first, with DCR as fallback
let fullInformation = await context.provider.clientInformation();
if (!fullInformation) {
fullInformation = await registerClient(context.serverUrl, {
metadata,
clientMetadata,
});
context.provider.saveClientInformation(fullInformation);
}

context.provider.saveClientInformation(fullInformation);
context.updateState({
oauthClientInfo: fullInformation,
oauthStep: "authorization_redirect",
Expand Down