diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md
index ef7ae6a944..171cb40fbe 100644
--- a/packages/frontend-defaults/report.api.md
+++ b/packages/frontend-defaults/report.api.md
@@ -41,6 +41,11 @@ export interface CreateAppOptions {
pluginInfoResolver?: FrontendPluginInfoResolver;
}
+// @public @deprecated (undocumented)
+export function createPublicSignInApp(options?: CreateAppOptions): {
+ createRoot(): JSX_2;
+};
+
// @public (undocumented)
export function discoverAvailableFeatures(config: Config): {
features: (FrontendFeature | FrontendFeatureLoader)[];
diff --git a/packages/frontend-defaults/src/createPublicSignInApp.test.tsx b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx
new file mode 100644
index 0000000000..58a264e81d
--- /dev/null
+++ b/packages/frontend-defaults/src/createPublicSignInApp.test.tsx
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ SignInPageBlueprint,
+ createFrontendModule,
+} from '@backstage/frontend-plugin-api';
+import { render, screen, waitFor } from '@testing-library/react';
+import { useEffect } from 'react';
+import { createPublicSignInApp } from './createPublicSignInApp';
+import { mockApis } from '@backstage/test-utils';
+
+describe('createPublicSignInApp', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should render a sign-in page', async () => {
+ const app = createPublicSignInApp({
+ configLoader: async () => ({ config: mockApis.config() }),
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [
+ SignInPageBlueprint.make({
+ params: {
+ loader: async () => () =>
Sign in page
,
+ },
+ }),
+ ],
+ }),
+ ],
+ });
+
+ render(app.createRoot());
+
+ await expect(
+ screen.findByText('Sign in page'),
+ ).resolves.toBeInTheDocument();
+ });
+
+ it('should render the form redirect on sign-in', async () => {
+ const submitSpy = jest
+ .spyOn(HTMLFormElement.prototype, 'submit')
+ .mockReturnValue();
+
+ const app = createPublicSignInApp({
+ configLoader: async () => ({ config: mockApis.config() }),
+ features: [
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [
+ SignInPageBlueprint.make({
+ params: {
+ loader:
+ async () =>
+ ({ onSignInSuccess }) => {
+ useEffect(() => {
+ onSignInSuccess(
+ mockApis.identity({ token: 'mock-token' }),
+ );
+ }, [onSignInSuccess]);
+ return ;
+ },
+ },
+ }),
+ ],
+ }),
+ ],
+ });
+
+ const { baseElement } = render(app.createRoot());
+
+ await waitFor(() => {
+ expect(submitSpy).toHaveBeenCalled();
+ });
+
+ expect(baseElement).toMatchInlineSnapshot(`
+
+
+
+
+
+ `);
+ });
+});
diff --git a/packages/frontend-defaults/src/createPublicSignInApp.tsx b/packages/frontend-defaults/src/createPublicSignInApp.tsx
new file mode 100644
index 0000000000..0015d9403f
--- /dev/null
+++ b/packages/frontend-defaults/src/createPublicSignInApp.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2025 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { appModulePublicSignIn } from '@backstage/plugin-app/alpha';
+import { CreateAppOptions, createApp } from './createApp';
+
+/**
+ * @public
+ * @deprecated Use {@link @backstage/plugin-app/alpha#appModulePublicSignIn} instead.
+ */
+export function createPublicSignInApp(options?: CreateAppOptions) {
+ return createApp({
+ ...options,
+ features: [...(options?.features ?? []), appModulePublicSignIn],
+ });
+}
diff --git a/packages/frontend-defaults/src/index.ts b/packages/frontend-defaults/src/index.ts
index 0897d9f38e..68d4c72568 100644
--- a/packages/frontend-defaults/src/index.ts
+++ b/packages/frontend-defaults/src/index.ts
@@ -21,5 +21,6 @@
*/
export { createApp, type CreateAppOptions } from './createApp';
+export { createPublicSignInApp } from './createPublicSignInApp';
export { discoverAvailableFeatures } from './discovery';
export { resolveAsyncFeatures } from './resolution';