Skip to content

Commit b2f3e36

Browse files
authored
feat: handle providerId in a generic way (#109)
* refactor: repalce from next public env var to OIDC_PROVIDER_ID * feat: pass providerId from server component to signin page * fix: show okta icon only for okta provider id * test: ensure the providerName format
1 parent 3244cff commit b2f3e36

File tree

11 files changed

+78
-58
lines changed

11 files changed

+78
-58
lines changed

.env.example

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ OIDC_CLIENT_ID=
1616
OIDC_CLIENT_SECRET=
1717

1818
# OIDC Provider identifier (e.g., "okta", "auth0", "oidc")
19-
# Must use NEXT_PUBLIC_ prefix (required for both server and client)
20-
NEXT_PUBLIC_OIDC_PROVIDER_ID=
19+
OIDC_PROVIDER_ID=
2120

2221
# Better Auth Configuration
2322
# Secret key for token encryption (generate with: openssl rand -base64 32)
@@ -49,6 +48,6 @@ API_BASE_URL=
4948
# OIDC_ISSUER_URL=http://localhost:3001
5049
# OIDC_CLIENT_ID=web-client
5150
# OIDC_CLIENT_SECRET=web-secret
52-
# NEXT_PUBLIC_OIDC_PROVIDER_ID=oidc
51+
# OIDC_PROVIDER_ID=oidc
5352
# BETTER_AUTH_URL=http://localhost:3000
5453
# API_BASE_URL=http://localhost:9090

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ pnpm generate-client # Fetch swagger.json and regenerate
330330
- `OIDC_ISSUER_URL` - OIDC provider URL
331331
- `OIDC_CLIENT_ID` - OAuth2 client ID
332332
- `OIDC_CLIENT_SECRET` - OAuth2 client secret
333-
- `NEXT_PUBLIC_OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - **Required**, must use `NEXT_PUBLIC_` prefix. Not sensitive data - it's just an identifier.
333+
- `OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - **Required**, server-side only.
334334
- `BETTER_AUTH_URL` - Application base URL
335335
- `BETTER_AUTH_SECRET` - Secret for token encryption
336336

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ git push origin v0.x.x
279279
- `OIDC_ISSUER_URL` - OIDC provider URL
280280
- `OIDC_CLIENT_ID` - OAuth2 client ID
281281
- `OIDC_CLIENT_SECRET` - OAuth2 client secret
282-
- `NEXT_PUBLIC_OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - Required, must use `NEXT_PUBLIC_` prefix. Not sensitive data - it's just an identifier.
282+
- `OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - Required, server-side only.
283283
- `BETTER_AUTH_URL` - Application base URL
284284
- `BETTER_AUTH_SECRET` - Secret for token encryption
285285

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ pnpm dev:mock-server
220220
OIDC_ISSUER_URL=https://your-oidc-provider.com
221221
OIDC_CLIENT_ID=your-client-id
222222
OIDC_CLIENT_SECRET=your-client-secret
223-
NEXT_PUBLIC_OIDC_PROVIDER_ID=okta # or your provider
223+
OIDC_PROVIDER_ID=okta # or your provider
224224
BETTER_AUTH_SECRET=your-secret
225225
BETTER_AUTH_URL=http://localhost:3000
226226
```
@@ -248,7 +248,7 @@ pnpm dev:next
248248
OIDC_ISSUER_URL=https://your-oidc-provider.com
249249
OIDC_CLIENT_ID=your-client-id
250250
OIDC_CLIENT_SECRET=your-client-secret
251-
NEXT_PUBLIC_OIDC_PROVIDER_ID=okta
251+
OIDC_PROVIDER_ID=okta
252252

253253
# Real backend API
254254
API_BASE_URL=https://your-backend-api.com
@@ -302,15 +302,15 @@ See [`docs/mocks.md`](./docs/mocks.md) for details.
302302

303303
### Required for Production
304304

305-
| Variable | Description | Example |
306-
| ------------------------------ | ---------------------------- | --------------------------------------- |
307-
| `OIDC_ISSUER_URL` | OIDC provider's issuer URL | `https://auth.example.com` |
308-
| `OIDC_CLIENT_ID` | OAuth2 client ID | `your-client-id` |
309-
| `OIDC_CLIENT_SECRET` | OAuth2 client secret | `your-client-secret` |
310-
| `NEXT_PUBLIC_OIDC_PROVIDER_ID` | Provider identifier (public) | `okta`, `auth0`, `oidc` |
311-
| `BETTER_AUTH_SECRET` | Secret for token encryption | Generate with `openssl rand -base64 32` |
312-
| `BETTER_AUTH_URL` | Application base URL | `https://your-app.example.com` |
313-
| `API_BASE_URL` | Backend API URL | `https://api.example.com` |
305+
| Variable | Description | Example |
306+
| -------------------- | --------------------------- | --------------------------------------- |
307+
| `OIDC_ISSUER_URL` | OIDC provider's issuer URL | `https://auth.example.com` |
308+
| `OIDC_CLIENT_ID` | OAuth2 client ID | `your-client-id` |
309+
| `OIDC_CLIENT_SECRET` | OAuth2 client secret | `your-client-secret` |
310+
| `OIDC_PROVIDER_ID` | Provider identifier | `okta`, `auth0`, `oidc` |
311+
| `BETTER_AUTH_SECRET` | Secret for token encryption | Generate with `openssl rand -base64 32` |
312+
| `BETTER_AUTH_URL` | Application base URL | `https://your-app.example.com` |
313+
| `API_BASE_URL` | Backend API URL | `https://api.example.com` |
314314

315315
### Optional
316316

@@ -327,7 +327,7 @@ NODE_ENV=development
327327
OIDC_ISSUER_URL=http://localhost:3001
328328
OIDC_CLIENT_ID=web-client
329329
OIDC_CLIENT_SECRET=web-secret
330-
NEXT_PUBLIC_OIDC_PROVIDER_ID=oidc
330+
OIDC_PROVIDER_ID=oidc
331331
BETTER_AUTH_URL=http://localhost:3000
332332
API_BASE_URL=http://localhost:9090
333333
```

dev-auth/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ Replace this with a real OIDC provider (Okta, Keycloak, Auth0, etc.) by updating
5353
- `OIDC_ISSUER_URL` - OIDC provider URL
5454
- `OIDC_CLIENT_ID` - OAuth2 client ID
5555
- `OIDC_CLIENT_SECRET` - OAuth2 client secret
56-
- `NEXT_PUBLIC_OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - **Required**, must use `NEXT_PUBLIC_` prefix. Not sensitive data - it's just an identifier.
56+
- `OIDC_PROVIDER_ID` - Provider identifier (e.g., "okta", "oidc") - **Required**, server-side only.
5757
- `BETTER_AUTH_URL` - Application base URL (e.g., `http://localhost:3000`)
5858
- `BETTER_AUTH_SECRET` - Secret for token encryption

src/app/signin/__tests__/signin.test.tsx

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { cleanup, render, screen, waitFor } from "@testing-library/react";
22
import userEvent from "@testing-library/user-event";
3+
import { toast } from "sonner";
34
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
45
import SignInPage from "@/app/signin/page";
6+
import { SignInButton } from "@/app/signin/signin-button";
7+
import { authClient } from "@/lib/auth/auth-client";
58

69
describe("SignInPage", () => {
710
beforeEach(() => {
@@ -33,18 +36,20 @@ describe("SignInPage", () => {
3336
}),
3437
).toBeDefined();
3538

36-
expect(screen.getByRole("button", { name: /Okta/i })).toBeDefined();
39+
expect(screen.getByRole("button", { name: /Oidc/i })).toBeDefined();
3740
});
3841

3942
test("calls authClient.signIn.oauth2 when button is clicked", async () => {
4043
const user = userEvent.setup();
41-
const { authClient } = await import("@/lib/auth/auth-client");
42-
vi.mocked(authClient.signIn.oauth2).mockResolvedValue({ error: null });
44+
vi.mocked(authClient.signIn.oauth2).mockResolvedValue({
45+
data: { url: "http://example.com", redirect: true },
46+
error: null,
47+
});
4348

4449
render(<SignInPage />);
4550

46-
const oktaButton = screen.getByRole("button", { name: /Okta/i });
47-
await user.click(oktaButton);
51+
const signInButton = screen.getByRole("button", { name: /Oidc/i });
52+
await user.click(signInButton);
4853

4954
await waitFor(() => {
5055
expect(authClient.signIn.oauth2).toHaveBeenCalledWith({
@@ -56,19 +61,16 @@ describe("SignInPage", () => {
5661

5762
test("shows error toast when signin fails with error", async () => {
5863
const user = userEvent.setup();
59-
const { toast } = await import("sonner");
60-
const { authClient } = await import("@/lib/auth/auth-client");
61-
6264
vi.mocked(authClient.signIn.oauth2).mockResolvedValue({
6365
error: {
6466
message: "Invalid credentials",
6567
},
66-
});
68+
} as Awaited<ReturnType<typeof authClient.signIn.oauth2>>);
6769

6870
render(<SignInPage />);
6971

70-
const oktaButton = screen.getByRole("button", { name: /Okta/i });
71-
await user.click(oktaButton);
72+
const signInButton = screen.getByRole("button", { name: /Oidc/i });
73+
await user.click(signInButton);
7274

7375
await waitFor(() => {
7476
expect(toast.error).toHaveBeenCalledWith("Signin failed", {
@@ -79,17 +81,14 @@ describe("SignInPage", () => {
7981

8082
test("shows error toast when signin throws exception", async () => {
8183
const user = userEvent.setup();
82-
const { toast } = await import("sonner");
83-
const { authClient } = await import("@/lib/auth/auth-client");
84-
8584
vi.mocked(authClient.signIn.oauth2).mockRejectedValue(
8685
new Error("Network error"),
8786
);
8887

8988
render(<SignInPage />);
9089

91-
const oktaButton = screen.getByRole("button", { name: /Okta/i });
92-
await user.click(oktaButton);
90+
const signInButton = screen.getByRole("button", { name: /Oidc/i });
91+
await user.click(signInButton);
9392

9493
await waitFor(() => {
9594
expect(toast.error).toHaveBeenCalledWith("Signin error", {
@@ -100,22 +99,41 @@ describe("SignInPage", () => {
10099

101100
test("shows generic error message for unknown errors", async () => {
102101
const user = userEvent.setup();
103-
const { toast } = await import("sonner");
104-
const { authClient } = await import("@/lib/auth/auth-client");
105-
106102
vi.mocked(authClient.signIn.oauth2).mockRejectedValue(
107103
"Something went wrong",
108104
);
109105

110106
render(<SignInPage />);
111107

112-
const oktaButton = screen.getByRole("button", { name: /Okta/i });
113-
await user.click(oktaButton);
108+
const signInButton = screen.getByRole("button", { name: /Oidc/i });
109+
await user.click(signInButton);
114110

115111
await waitFor(() => {
116112
expect(toast.error).toHaveBeenCalledWith("Signin error", {
117113
description: "An unexpected error occurred",
118114
});
119115
});
120116
});
117+
118+
test("signin with okta provider", async () => {
119+
const user = userEvent.setup();
120+
vi.mocked(authClient.signIn.oauth2).mockResolvedValue({
121+
data: { url: "http://example.com", redirect: true },
122+
error: null,
123+
});
124+
125+
render(<SignInButton providerId="okta" />);
126+
127+
expect(screen.getByRole("button", { name: "Okta" })).toBeDefined();
128+
129+
const oktaButton = screen.getByRole("button", { name: /Okta/i });
130+
await user.click(oktaButton);
131+
132+
await waitFor(() => {
133+
expect(authClient.signIn.oauth2).toHaveBeenCalledWith({
134+
providerId: "okta",
135+
callbackURL: "/catalog",
136+
});
137+
});
138+
});
121139
});

src/app/signin/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ToolHiveIcon } from "@/components/icons";
2+
import { OIDC_PROVIDER_ID } from "@/lib/auth/constants";
23
import { SignInButton } from "./signin-button";
34

45
export default function SignInPage() {
@@ -20,7 +21,7 @@ export default function SignInPage() {
2021
</p>
2122
</div>
2223

23-
<SignInButton />
24+
<SignInButton providerId={OIDC_PROVIDER_ID} />
2425
</div>
2526
</div>
2627
</div>

src/app/signin/signin-button.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ import Image from "next/image";
33
import { toast } from "sonner";
44
import { Button } from "@/components/ui/button";
55
import { authClient } from "@/lib/auth/auth-client";
6-
import { OIDC_PROVIDER_ID } from "@/lib/auth/constants";
7-
export function SignInButton() {
6+
7+
export function SignInButton({ providerId }: { providerId: string }) {
8+
const isOktaProvider = providerId === "okta";
9+
const providerName = providerId.charAt(0).toUpperCase() + providerId.slice(1);
10+
811
const handleOIDCSignIn = async () => {
912
try {
1013
const { error } = await authClient.signIn.oauth2({
11-
providerId: OIDC_PROVIDER_ID,
14+
providerId,
1215
callbackURL: "/catalog",
1316
});
1417

@@ -36,14 +39,16 @@ export function SignInButton() {
3639
className="w-full h-9 gap-2"
3740
size="default"
3841
>
39-
<Image
40-
src="/okta-icon.svg"
41-
alt="Okta"
42-
width={16}
43-
height={16}
44-
className="shrink-0"
45-
/>
46-
<span>Okta</span>
42+
{isOktaProvider && (
43+
<Image
44+
src="/okta-icon.svg"
45+
alt={providerName}
46+
width={16}
47+
height={16}
48+
className="shrink-0"
49+
/>
50+
)}
51+
<span>{providerName}</span>
4752
</Button>
4853
);
4954
}

src/lib/auth/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ When making an API call:
123123
OIDC_ISSUER_URL=https://your-okta-domain.okta.com
124124
OIDC_CLIENT_ID=your-client-id
125125
OIDC_CLIENT_SECRET=your-client-secret
126-
NEXT_PUBLIC_OIDC_PROVIDER_ID=okta
126+
OIDC_PROVIDER_ID=okta
127127

128128
# Better Auth
129129
BETTER_AUTH_URL=http://localhost:3000

src/lib/auth/constants.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@
55
// Environment configuration
66
/**
77
* OIDC Provider ID (e.g., "oidc", "okta")
8-
* Must use NEXT_PUBLIC_ prefix as it's needed both server-side (auth.ts) and client-side (signin page).
9-
* Note: This exposes the provider name to the client, which is generally acceptable
10-
* but does reveal infrastructure details.
8+
* Server-side only - not exposed to the client.
119
*/
12-
export const OIDC_PROVIDER_ID =
13-
process.env.NEXT_PUBLIC_OIDC_PROVIDER_ID || "oidc";
10+
export const OIDC_PROVIDER_ID = process.env.OIDC_PROVIDER_ID || "oidc";
1411
export const OIDC_ISSUER_URL = process.env.OIDC_ISSUER_URL || "";
1512
export const OIDC_CLIENT_ID = process.env.OIDC_CLIENT_ID || "";
1613
export const OIDC_CLIENT_SECRET = process.env.OIDC_CLIENT_SECRET || "";

0 commit comments

Comments
 (0)