@imtf/auth 1.0.1-beta.3 → 1.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,7 @@
1
- const PROVIDER_ID = "oidc-provider";
2
- export {
3
- PROVIDER_ID
4
- };
1
+ //#region src/constants.ts
2
+ /**
3
+ * Generic OIDC provider identifier used for authentication.
4
+ */
5
+ var PROVIDER_ID = "oidc-provider";
6
+ //#endregion
7
+ export { PROVIDER_ID };
@@ -1,57 +1,68 @@
1
+ "use client";
1
2
  import { useCallback } from "react";
2
- function useSignOut({
3
- postSignOutRedirectPath = "/auth/sign-in?sign-out=success",
4
- retrieveSignOutUrlPath = "/api/auth/sign-out-url",
5
- onSignOutStart,
6
- onSignOutSuccess,
7
- authClient,
8
- origin: providedOrigin
9
- }) {
10
- const origin = providedOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
11
- const standardSignOut = useCallback(() => {
12
- onSignOutStart?.();
13
- void authClient.signOut({
14
- fetchOptions: {
15
- onSuccess: onSignOutSuccess
16
- }
17
- });
18
- }, [authClient, onSignOutStart, onSignOutSuccess]);
19
- const handleSignOut = useCallback(async () => {
20
- const postLogoutRedirectUri = `${origin}${postSignOutRedirectPath}`;
21
- try {
22
- const response = await fetch(
23
- `${retrieveSignOutUrlPath}?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`
24
- );
25
- if (!response.ok) {
26
- const { error } = await response.json().catch(() => ({ error: null }));
27
- throw new Error(error ?? "Failed to create logout URL");
28
- }
29
- const { url } = await response.json();
30
- if (!url) {
31
- standardSignOut();
32
- return;
33
- }
34
- await authClient.signOut();
35
- if (typeof window === "undefined") {
36
- return;
37
- }
38
- window.location.href = url;
39
- } catch (error) {
40
- console.error(
41
- "Error during federated logout, switching to standard logout:",
42
- error
43
- );
44
- standardSignOut();
45
- }
46
- }, [
47
- authClient,
48
- origin,
49
- postSignOutRedirectPath,
50
- retrieveSignOutUrlPath,
51
- standardSignOut
52
- ]);
53
- return { handleSignOut };
3
+ //#region src/hooks/useSignOut.tsx
4
+ /**
5
+ * Custom hook for handling user sign-out with support for federated logout.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const { handleSignOut } = useSignOut({ postSignOutRedirectPath: "/auth/sign-in?sign-out=success" });
10
+ * ```
11
+ */
12
+ function useSignOut({ postSignOutRedirectPath = "/auth/sign-in?sign-out=success", retrieveSignOutUrlPath = "/api/auth/sign-out-url", onSignOutStart, onSignOutSuccess, authClient, origin: providedOrigin }) {
13
+ const origin = providedOrigin ?? (typeof window !== "undefined" ? window.location.origin : "");
14
+ /**
15
+ * Performs a standard (non-federated) sign-out and redirects to the sign-in page.
16
+ */
17
+ const standardSignOut = useCallback(() => {
18
+ onSignOutStart?.();
19
+ authClient.signOut({ fetchOptions: { onSuccess: onSignOutSuccess } });
20
+ }, [
21
+ authClient,
22
+ onSignOutStart,
23
+ onSignOutSuccess
24
+ ]);
25
+ return { handleSignOut: useCallback(async () => {
26
+ const postLogoutRedirectUri = `${origin}${postSignOutRedirectPath}`;
27
+ /**
28
+ * Attempt federated logout. This is, surprisingly, not built into better-auth:
29
+ */
30
+ try {
31
+ /**
32
+ * Create a logout URL before we invoke authClient.signOut(), since it clears critical tokens.
33
+ */
34
+ const response = await fetch(`${retrieveSignOutUrlPath}?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirectUri)}`);
35
+ if (!response.ok) {
36
+ const { error } = await response.json().catch(() => ({ error: null }));
37
+ throw new Error(error ?? "Failed to create logout URL");
38
+ }
39
+ const { url } = await response.json();
40
+ if (!url) {
41
+ standardSignOut();
42
+ return;
43
+ }
44
+ /**
45
+ * Sign out from better-auth, then redirect the user to the OIDC provider's
46
+ * end_session_endpoint to terminate the federated session. The provider
47
+ * should automatically redirect back to the application after logout.
48
+ */
49
+ await authClient.signOut();
50
+ if (typeof window === "undefined") return;
51
+ window.location.href = url;
52
+ } catch (error) {
53
+ console.error("Error during federated logout, switching to standard logout:", error);
54
+ /**
55
+ * Fallback: Standard logout
56
+ */
57
+ standardSignOut();
58
+ }
59
+ }, [
60
+ authClient,
61
+ origin,
62
+ postSignOutRedirectPath,
63
+ retrieveSignOutUrlPath,
64
+ standardSignOut
65
+ ]) };
54
66
  }
55
- export {
56
- useSignOut
57
- };
67
+ //#endregion
68
+ export { useSignOut };
package/lib/index.esm.js CHANGED
@@ -1,12 +1,6 @@
1
+ import { PROVIDER_ID } from "./constants.esm.js";
1
2
  import { getBetterAuthOptions } from "./options/auth.esm.js";
2
3
  import { getBetterAuthClientOptions } from "./options/auth-client.esm.js";
3
4
  import { getSignOutUrlRoute } from "./routes/getSignOutUrlRoute.esm.js";
4
- import { PROVIDER_ID } from "./constants.esm.js";
5
5
  import { useSignOut } from "./hooks/useSignOut.esm.js";
6
- export {
7
- PROVIDER_ID,
8
- getBetterAuthClientOptions,
9
- getBetterAuthOptions,
10
- getSignOutUrlRoute,
11
- useSignOut
12
- };
6
+ export { PROVIDER_ID, getBetterAuthClientOptions, getBetterAuthOptions, getSignOutUrlRoute, useSignOut };
@@ -1,9 +1,8 @@
1
+ "use client";
1
2
  import { genericOAuthClient } from "better-auth/client/plugins";
3
+ //#region src/options/auth-client.ts
2
4
  function getBetterAuthClientOptions() {
3
- return {
4
- plugins: [genericOAuthClient()]
5
- };
5
+ return { plugins: [genericOAuthClient()] };
6
6
  }
7
- export {
8
- getBetterAuthClientOptions
9
- };
7
+ //#endregion
8
+ export { getBetterAuthClientOptions };
@@ -1,61 +1,42 @@
1
+ import { PROVIDER_ID } from "../constants.esm.js";
1
2
  import { nextCookies } from "better-auth/next-js";
2
3
  import { genericOAuth } from "better-auth/plugins";
3
- import { PROVIDER_ID } from "../constants.esm.js";
4
- function getBetterAuthOptions({
5
- oidcDiscoveryUrl,
6
- authSecret,
7
- authBaseURL,
8
- authClientId,
9
- authClientSecret,
10
- providerId = PROVIDER_ID
11
- }) {
12
- return {
13
- secret: authSecret,
14
- baseURL: authBaseURL,
15
- session: {
16
- cookieCache: {
17
- enabled: true,
18
- version: "1",
19
- maxAge: 60 * 60 * 24,
20
- // Cache duration in seconds (1 day)
21
- strategy: "compact",
22
- refreshCache: true
23
- // Enable stateless refresh
24
- },
25
- expiresIn: 60 * 60 * 24,
26
- // Session expiration (1 day)
27
- updateAge: 60 * 60,
28
- // How often to update the session (1 hour)
29
- freshAge: 60 * 5
30
- // Consider session fresh for 5 minutes
31
- },
32
- account: {
33
- updateAccountOnSignIn: true,
34
- storeStateStrategy: "cookie",
35
- storeAccountCookie: true
36
- // Store account data in cookie (required for database-less flows)
37
- },
38
- plugins: [
39
- genericOAuth({
40
- config: [
41
- {
42
- providerId,
43
- clientId: authClientId,
44
- clientSecret: authClientSecret,
45
- discoveryUrl: oidcDiscoveryUrl,
46
- scopes: ["openid"],
47
- pkce: true
48
- }
49
- ]
50
- }),
51
- // Next.js cookie integration for server actions and middleware
52
- nextCookies()
53
- ],
54
- onAPIError: {
55
- errorURL: "/auth/error"
56
- }
57
- };
4
+ //#region src/options/auth.ts
5
+ function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId = PROVIDER_ID }) {
6
+ /**
7
+ * Better-auth configuration with OIDC/OAuth2 support.
8
+ * Uses cookie-based session storage for database-less authentication.
9
+ */
10
+ return {
11
+ secret: authSecret,
12
+ baseURL: authBaseURL,
13
+ session: {
14
+ cookieCache: {
15
+ enabled: true,
16
+ version: "1",
17
+ maxAge: 3600 * 24,
18
+ strategy: "jwe",
19
+ refreshCache: true
20
+ },
21
+ expiresIn: 3600 * 24,
22
+ updateAge: 3600,
23
+ freshAge: 300
24
+ },
25
+ account: {
26
+ updateAccountOnSignIn: true,
27
+ storeStateStrategy: "cookie",
28
+ storeAccountCookie: true
29
+ },
30
+ plugins: [genericOAuth({ config: [{
31
+ providerId,
32
+ clientId: authClientId,
33
+ clientSecret: authClientSecret,
34
+ discoveryUrl: oidcDiscoveryUrl,
35
+ scopes: ["openid"],
36
+ pkce: true
37
+ }] }), nextCookies()],
38
+ onAPIError: { errorURL: "/auth/error" }
39
+ };
58
40
  }
59
- export {
60
- getBetterAuthOptions
61
- };
41
+ //#endregion
42
+ export { getBetterAuthOptions };
@@ -1,60 +1,48 @@
1
1
  import { PROVIDER_ID } from "../constants.esm.js";
2
- function getSignOutUrlRoute({
3
- auth,
4
- discoveryUrl,
5
- federatedLogout = true,
6
- providerId = PROVIDER_ID,
7
- headers
8
- }) {
9
- return async function GET(request) {
10
- try {
11
- if (!federatedLogout || !discoveryUrl) {
12
- return Response.json({ url: null }, { status: 200 });
13
- }
14
- const token = await auth.api.getAccessToken({
15
- body: {
16
- providerId
17
- },
18
- headers: await headers()
19
- }).catch(() => null);
20
- if (!token?.idToken) {
21
- return Response.json(
22
- { error: "No ID token found for user" },
23
- { status: 400 }
24
- );
25
- }
26
- const wellKnownResponse = await fetch(discoveryUrl);
27
- if (!wellKnownResponse.ok) {
28
- return Response.json(
29
- { error: "Failed to fetch OIDC discovery document" },
30
- { status: 500 }
31
- );
32
- }
33
- const wellKnownConfig = await wellKnownResponse.json();
34
- const endSessionEndpoint = wellKnownConfig.end_session_endpoint;
35
- if (!endSessionEndpoint) {
36
- return Response.json(
37
- { error: "Could not determine end_session_endpoint" },
38
- { status: 500 }
39
- );
40
- }
41
- const logoutUrl = new URL(endSessionEndpoint);
42
- const postLogoutRedirectUri = request.nextUrl.searchParams.get("post_logout_redirect_uri") ?? "/auth/sign-in";
43
- logoutUrl.searchParams.set(
44
- "post_logout_redirect_uri",
45
- postLogoutRedirectUri
46
- );
47
- logoutUrl.searchParams.set("id_token_hint", token.idToken);
48
- return Response.json({ url: logoutUrl.toString() });
49
- } catch (error) {
50
- console.error("Failed to construct logout URL:", error);
51
- return Response.json(
52
- { error: "Failed to construct logout URL" },
53
- { status: 500 }
54
- );
55
- }
56
- };
2
+ //#region src/routes/getSignOutUrlRoute.ts
3
+ function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout = true, providerId = PROVIDER_ID, headers }) {
4
+ /**
5
+ * API route that constructs a federated logout URL for OIDC providers.
6
+ * Returns null if federated logout is disabled.
7
+ *
8
+ * @param request - Next.js request object with optional post_logout_redirect_uri parameter
9
+ * @returns JSON response with logout URL or error
10
+ */
11
+ return async function GET(request) {
12
+ try {
13
+ if (!federatedLogout || !discoveryUrl) return Response.json({ url: null }, { status: 200 });
14
+ /**
15
+ * Retrieve the user's ID token, to produce a valid logout URL.
16
+ */
17
+ const token = await auth.api.getAccessToken({
18
+ body: { providerId },
19
+ headers: await headers()
20
+ }).catch(() => null);
21
+ if (!token?.idToken) return Response.json({ error: "No ID token found for user" }, { status: 400 });
22
+ /**
23
+ * Fetch the well-known configuration from the OIDC provider to construct the
24
+ * logout URL.
25
+ */
26
+ const wellKnownResponse = await fetch(discoveryUrl);
27
+ if (!wellKnownResponse.ok) return Response.json({ error: "Failed to fetch OIDC discovery document" }, { status: 500 });
28
+ const endSessionEndpoint = (await wellKnownResponse.json()).end_session_endpoint;
29
+ if (!endSessionEndpoint) return Response.json({ error: "Could not determine end_session_endpoint" }, { status: 500 });
30
+ /**
31
+ * Construct the logout URL with parameters.
32
+ * - end_session_endpoint: The OIDC provider's end_session_endpoint.
33
+ * - post_logout_redirect_uri: The URL to redirect to after logout.
34
+ * - id_token_hint: The user's ID token.
35
+ */
36
+ const logoutUrl = new URL(endSessionEndpoint);
37
+ const postLogoutRedirectUri = request.nextUrl.searchParams.get("post_logout_redirect_uri") ?? "/auth/sign-in";
38
+ logoutUrl.searchParams.set("post_logout_redirect_uri", postLogoutRedirectUri);
39
+ logoutUrl.searchParams.set("id_token_hint", token.idToken);
40
+ return Response.json({ url: logoutUrl.toString() });
41
+ } catch (error) {
42
+ console.error("Failed to construct logout URL:", error);
43
+ return Response.json({ error: "Failed to construct logout URL" }, { status: 500 });
44
+ }
45
+ };
57
46
  }
58
- export {
59
- getSignOutUrlRoute
60
- };
47
+ //#endregion
48
+ export { getSignOutUrlRoute };
@@ -33,6 +33,6 @@ export type UseSignOutOptions = {
33
33
  * const { handleSignOut } = useSignOut({ postSignOutRedirectPath: "/auth/sign-in?sign-out=success" });
34
34
  * ```
35
35
  */
36
- export declare function useSignOut({ postSignOutRedirectPath, retrieveSignOutUrlPath, onSignOutStart, onSignOutSuccess, authClient, origin: providedOrigin, }: UseSignOutOptions): {
36
+ export declare function useSignOut({ postSignOutRedirectPath, retrieveSignOutUrlPath, onSignOutStart, onSignOutSuccess, authClient, origin: providedOrigin }: UseSignOutOptions): {
37
37
  handleSignOut: () => Promise<void>;
38
38
  };
@@ -1,8 +1,10 @@
1
1
  export declare function getBetterAuthClientOptions(): {
2
2
  readonly plugins: [{
3
3
  id: "generic-oauth-client";
4
+ version: string;
4
5
  $InferServerPlugin: ReturnType<(options: import("better-auth/plugins").GenericOAuthOptions) => {
5
6
  id: "generic-oauth";
7
+ version: string;
6
8
  init: (ctx: import("better-auth").AuthContext) => {
7
9
  context: {
8
10
  socialProviders: import("better-auth").OAuthProvider<Record<string, any>, Partial<import("better-auth").ProviderOptions<any>>>[];
@@ -20,7 +22,7 @@ export declare function getBetterAuthClientOptions(): {
20
22
  scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
21
23
  requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
22
24
  additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
23
- }, import("better-auth").$strip>;
25
+ }, import("zod/v4/core").$strip>;
24
26
  metadata: {
25
27
  openapi: {
26
28
  description: string;
@@ -58,7 +60,7 @@ export declare function getBetterAuthClientOptions(): {
58
60
  error_description: import("zod").ZodOptional<import("zod").ZodString>;
59
61
  state: import("zod").ZodOptional<import("zod").ZodString>;
60
62
  iss: import("zod").ZodOptional<import("zod").ZodString>;
61
- }, import("better-auth").$strip>;
63
+ }, import("zod/v4/core").$strip>;
62
64
  metadata: {
63
65
  allowedMediaTypes: string[];
64
66
  openapi: {
@@ -91,7 +93,7 @@ export declare function getBetterAuthClientOptions(): {
91
93
  callbackURL: import("zod").ZodString;
92
94
  scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
93
95
  errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
94
- }, import("better-auth").$strip>;
96
+ }, import("zod/v4/core").$strip>;
95
97
  use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
96
98
  session: {
97
99
  session: Record<string, any> & {
@@ -1,4 +1,4 @@
1
- export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId, }: {
1
+ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, authBaseURL, authClientId, authClientSecret, providerId }: {
2
2
  oidcDiscoveryUrl: string;
3
3
  authSecret: string;
4
4
  authClientId: string;
@@ -13,7 +13,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
13
13
  readonly enabled: true;
14
14
  readonly version: "1";
15
15
  readonly maxAge: number;
16
- readonly strategy: "compact";
16
+ readonly strategy: "jwe";
17
17
  readonly refreshCache: true;
18
18
  };
19
19
  readonly expiresIn: number;
@@ -27,6 +27,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
27
27
  };
28
28
  readonly plugins: [{
29
29
  id: "generic-oauth";
30
+ version: string;
30
31
  init: (ctx: import("better-auth").AuthContext) => {
31
32
  context: {
32
33
  socialProviders: import("better-auth").OAuthProvider<Record<string, any>, Partial<import("better-auth").ProviderOptions<any>>>[];
@@ -44,7 +45,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
44
45
  scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
45
46
  requestSignUp: import("zod").ZodOptional<import("zod").ZodBoolean>;
46
47
  additionalData: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
47
- }, import("better-auth").$strip>;
48
+ }, import("zod/v4/core").$strip>;
48
49
  metadata: {
49
50
  openapi: {
50
51
  description: string;
@@ -82,7 +83,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
82
83
  error_description: import("zod").ZodOptional<import("zod").ZodString>;
83
84
  state: import("zod").ZodOptional<import("zod").ZodString>;
84
85
  iss: import("zod").ZodOptional<import("zod").ZodString>;
85
- }, import("better-auth").$strip>;
86
+ }, import("zod/v4/core").$strip>;
86
87
  metadata: {
87
88
  allowedMediaTypes: string[];
88
89
  openapi: {
@@ -115,7 +116,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
115
116
  callbackURL: import("zod").ZodString;
116
117
  scopes: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
117
118
  errorCallbackURL: import("zod").ZodOptional<import("zod").ZodString>;
118
- }, import("better-auth").$strip>;
119
+ }, import("zod/v4/core").$strip>;
119
120
  use: ((inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
120
121
  session: {
121
122
  session: Record<string, any> & {
@@ -187,6 +188,7 @@ export declare function getBetterAuthOptions({ oidcDiscoveryUrl, authSecret, aut
187
188
  };
188
189
  }, {
189
190
  id: "next-cookies";
191
+ version: string;
190
192
  hooks: {
191
193
  before: {
192
194
  matcher(ctx: import("better-auth").HookEndpointContext): boolean;
@@ -1,7 +1,7 @@
1
1
  import type { Auth } from "better-auth";
2
2
  import { headers as nextHeaders } from "next/headers";
3
3
  import type { NextRequest } from "next/server";
4
- export declare function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout, providerId, headers, }: {
4
+ export declare function getSignOutUrlRoute({ auth, discoveryUrl, federatedLogout, providerId, headers }: {
5
5
  auth: Auth;
6
6
  discoveryUrl?: string;
7
7
  federatedLogout?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imtf/auth",
3
- "version": "1.0.1-beta.3",
3
+ "version": "1.1.0-beta.2",
4
4
  "description": "Better auth wrapper for imtf usage",
5
5
  "license": "UNLICENSED",
6
6
  "author": "",
@@ -9,13 +9,13 @@
9
9
  "prebuild": "npm run build:clean",
10
10
  "build:clean": "rimraf lib",
11
11
  "build:js": "vite build",
12
- "build:types": "tsc --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
12
+ "build:types": "tsgo --project ./tsconfig.build.json && tsc-alias -p ./tsconfig.build.json",
13
13
  "build": "concurrently npm:build:js npm:build:types",
14
14
  "lint": "eslint src --cache --report-unused-disable-directives --max-warnings 0",
15
15
  "test": "vitest",
16
16
  "test:ui": "vitest --ui --coverage",
17
17
  "test:coverage": "vitest run --coverage",
18
- "typecheck": "tsc --noEmit",
18
+ "typecheck": "tsgo --noEmit",
19
19
  "checks": "concurrently --kill-others-on-fail npm:lint npm:typecheck",
20
20
  "prepublishOnly": "npm run build && npm run checks"
21
21
  },
@@ -33,18 +33,18 @@
33
33
  }
34
34
  },
35
35
  "dependencies": {
36
- "@imtf/panache": "^5.6.0"
36
+ "@imtf/panache": "^5.9.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@mui/lab": "^7.0.1-beta.18",
40
40
  "@mui/material": "^7.1.0",
41
41
  "react": "^18.0.0 || ^19.0.0",
42
42
  "react-dom": "^18.0.0 || ^19.0.0",
43
- "better-auth": "^1.4.18"
43
+ "better-auth": "^1.5.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@mui/lab": "^7.0.1-beta.18",
47
47
  "@mui/material": "^7.1.0",
48
- "better-auth": "^1.4.18"
48
+ "better-auth": "^1.5.3"
49
49
  }
50
50
  }