Skip to content

Commit def1d47

Browse files
committed
feat: enhance authentication flow by refactoring SignInButton and SignOutButton, adding loading states, and improving error handling
chore: remove unused authRouter file and clean up middleware logic fix: update AsgardeoContext and related types to support optional properties
1 parent 5eca0fb commit def1d47

File tree

12 files changed

+158
-188
lines changed

12 files changed

+158
-188
lines changed

packages/nextjs/src/AsgardeoNextClient.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {NextRequest, NextResponse} from 'next/server';
4040
import {AsgardeoNextConfig} from './models/config';
4141
import getSessionId from './server/actions/getSessionId';
4242
import decorateConfigWithNextEnv from './utils/decorateConfigWithNextEnv';
43-
import authRouter from './server/actions/authRouter';
4443

4544
const removeTrailingSlash = (path: string): string => (path.endsWith('/') ? path.slice(0, -1) : path);
4645
/**

packages/nextjs/src/client/components/actions/SignInButton/SignInButton.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
'use client';
2020

21-
import {forwardRef, ForwardRefExoticComponent, ReactElement, Ref, RefAttributes} from 'react';
21+
import {forwardRef, ForwardRefExoticComponent, ReactElement, Ref, RefAttributes, useState, MouseEvent} from 'react';
22+
import {AsgardeoRuntimeError} from '@asgardeo/node';
2223
import {BaseSignInButton, BaseSignInButtonProps, useTranslation} from '@asgardeo/react';
2324
import useAsgardeo from '../../../../client/contexts/Asgardeo/useAsgardeo';
2425
import {useRouter} from 'next/navigation';
@@ -54,22 +55,39 @@ export type SignInButtonProps = BaseSignInButtonProps;
5455
*/
5556
const SignInButton = forwardRef<HTMLButtonElement, SignInButtonProps>(
5657
(
57-
{className, style, children, preferences, ...rest}: SignInButtonProps,
58+
{className, style, children, preferences, onClick, ...rest}: SignInButtonProps,
5859
ref: Ref<HTMLButtonElement>,
5960
): ReactElement => {
6061
const {signIn, signInUrl} = useAsgardeo();
6162
const router = useRouter();
6263
const {t} = useTranslation(preferences?.i18n);
6364

64-
const handleOnSubmit = (...args) => {
65-
console.log('SignInButton: handleOnSubmit called with: ', signInUrl);
66-
if (signInUrl) {
67-
router.push(signInUrl);
65+
const [isLoading, setIsLoading] = useState(false);
6866

69-
return;
70-
}
67+
const handleOnClick = async (e: MouseEvent<HTMLButtonElement>): Promise<void> => {
68+
try {
69+
setIsLoading(true);
70+
71+
// If a custom `signInUrl` is provided, use it for navigation.
72+
if (signInUrl) {
73+
router.push(signInUrl);
74+
} else {
75+
await signIn();
76+
}
7177

72-
signIn();
78+
if (onClick) {
79+
onClick(e);
80+
}
81+
} catch (error) {
82+
throw new AsgardeoRuntimeError(
83+
`Sign in failed: ${error instanceof Error ? error.message : String(error)}`,
84+
'SignInButton-handleSignIn-RuntimeError-001',
85+
'next',
86+
'Something went wrong while trying to sign in. Please try again later.',
87+
);
88+
} finally {
89+
setIsLoading(false);
90+
}
7391
};
7492

7593
return (
@@ -78,8 +96,7 @@ const SignInButton = forwardRef<HTMLButtonElement, SignInButtonProps>(
7896
style={style}
7997
ref={ref}
8098
preferences={preferences}
81-
type="submit"
82-
onClick={handleOnSubmit}
99+
onClick={handleOnClick}
83100
{...rest}
84101
>
85102
{children ?? t('elements.buttons.signIn')}

packages/nextjs/src/client/components/actions/SignOutButton/SignOutButton.tsx

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@
1818

1919
'use client';
2020

21-
import {FC, forwardRef, PropsWithChildren, ReactElement, Ref} from 'react';
22-
import {BaseSignOutButton, BaseSignOutButtonProps} from '@asgardeo/react';
21+
import {FC, forwardRef, PropsWithChildren, ReactElement, Ref, useState, MouseEvent} from 'react';
22+
import {BaseSignOutButton, BaseSignOutButtonProps, useTranslation} from '@asgardeo/react';
23+
import {AsgardeoRuntimeError} from '@asgardeo/node';
2324
import useAsgardeo from '../../../../client/contexts/Asgardeo/useAsgardeo';
2425

2526
/**
@@ -45,21 +46,42 @@ export type SignOutButtonProps = BaseSignOutButtonProps;
4546
* ```
4647
*/
4748
const SignOutButton = forwardRef<HTMLButtonElement, SignOutButtonProps>(
48-
({className, style, ...rest}: SignOutButtonProps, ref: Ref<HTMLButtonElement>): ReactElement => {
49+
({className, style, preferences, onClick, children, ...rest}: SignOutButtonProps, ref: Ref<HTMLButtonElement>): ReactElement => {
4950
const {signOut} = useAsgardeo();
51+
const {t} = useTranslation(preferences?.i18n);
52+
53+
const [isLoading, setIsLoading] = useState(false);
54+
55+
const handleOnClick = async (e: MouseEvent<HTMLButtonElement>): Promise<void> => {
56+
try {
57+
setIsLoading(true);
58+
await signOut();
59+
60+
if (onClick) {
61+
onClick(e);
62+
}
63+
} catch (error) {
64+
throw new AsgardeoRuntimeError(
65+
`Sign out failed: ${error instanceof Error ? error.message : String(error)}`,
66+
'SignOutButton-handleOnClick-RuntimeError-001',
67+
'next',
68+
'Something went wrong while trying to sign out. Please try again later.',
69+
);
70+
} finally {
71+
setIsLoading(false);
72+
}
73+
};
5074

5175
return (
5276
<BaseSignOutButton
53-
className={className}
54-
style={style}
5577
ref={ref}
56-
type="submit"
57-
onClick={() => {
58-
console.log('[SignOutButton] signOut called');
59-
signOut();
60-
}}
78+
onClick={handleOnClick}
79+
isLoading={isLoading}
80+
preferences={preferences}
6181
{...rest}
62-
/>
82+
>
83+
{children ?? t('elements.buttons.signOut')}
84+
</BaseSignOutButton>
6385
);
6486
},
6587
);

packages/nextjs/src/client/contexts/Asgardeo/AsgardeoContext.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,23 @@ import {Context, createContext} from 'react';
2525
/**
2626
* Props interface of {@link AsgardeoContext}
2727
*/
28-
export type AsgardeoContextProps = AsgardeoReactContextProps;
28+
export type AsgardeoContextProps = Partial<AsgardeoReactContextProps>;
2929

3030
/**
3131
* Context object for managing the Authentication flow builder core context.
3232
*/
33-
const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null | AsgardeoContextProps>({});
33+
const AsgardeoContext: Context<AsgardeoContextProps | null> = createContext<null | AsgardeoContextProps>({
34+
signInUrl: undefined,
35+
afterSignInUrl: undefined,
36+
baseUrl: undefined,
37+
isInitialized: false,
38+
isLoading: true,
39+
isSignedIn: false,
40+
signIn: null,
41+
signOut: null,
42+
signUp: null,
43+
user: null,
44+
});
3445

3546
AsgardeoContext.displayName = 'AsgardeoContext';
3647

packages/nextjs/src/client/contexts/Asgardeo/AsgardeoProvider.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ import {EmbeddedFlowExecuteRequestConfig, EmbeddedSignInFlowHandleRequestPayload
2222
import {I18nProvider, FlowProvider, UserProvider, ThemeProvider, AsgardeoProviderProps} from '@asgardeo/react';
2323
import {FC, PropsWithChildren, useEffect, useMemo, useState} from 'react';
2424
import {useRouter} from 'next/navigation';
25-
import AsgardeoContext from './AsgardeoContext';
25+
import AsgardeoContext, {AsgardeoContextProps} from './AsgardeoContext';
2626
import {getIsSignedInAction, getUserAction} from '../../../server/actions/authActions';
2727

2828
/**
2929
* Props interface of {@link AsgardeoClientProvider}
3030
*/
31-
export type AsgardeoClientProviderProps = AsgardeoProviderProps;
31+
export type AsgardeoClientProviderProps = Partial<Omit<AsgardeoProviderProps, 'baseUrl' | 'clientId'>> & Pick<AsgardeoProviderProps, 'baseUrl' | 'clientId'> & {
32+
signOut: AsgardeoContextProps['signOut'];
33+
signIn: AsgardeoContextProps['signIn'];
34+
};
3235

3336
const AsgardeoClientProvider: FC<PropsWithChildren<AsgardeoClientProviderProps>> = ({
3437
children,

packages/nextjs/src/middleware/asgardeoMiddleware.ts

Lines changed: 69 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import {asgardeoMiddleware} from '@asgardeo/nextjs/middleware';
21
/**
32
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
43
*
@@ -77,73 +76,75 @@ const asgardeoMiddleware = (
7776

7877
const asgardeoClient = AsgardeoNextClient.getInstance();
7978

80-
// Initialize client if not already done
81-
if (!asgardeoClient.isInitialized && resolvedOptions) {
82-
asgardeoClient.initialize(resolvedOptions);
83-
}
84-
85-
// Debug logging
86-
if (resolvedOptions.debug) {
87-
console.log(`[Asgardeo Middleware] Processing request: ${request.nextUrl.pathname}`);
88-
}
89-
90-
// Handle auth API routes automatically
91-
if (request.nextUrl.pathname.startsWith('/api/auth/asgardeo')) {
92-
if (resolvedOptions.debug) {
93-
console.log(`[Asgardeo Middleware] Handling auth route: ${request.nextUrl.pathname}`);
94-
}
95-
return await asgardeoClient.handleAuthRequest(request);
96-
}
97-
98-
// Create auth object for the handler
99-
const auth: AsgardeoAuth = {
100-
protect: async (options?: {redirect?: string}) => {
101-
const isSignedIn = await asgardeoClient.isSignedIn(request);
102-
if (!isSignedIn) {
103-
const afterSignInUrl = options?.redirect || '/api/auth/asgardeo/signin';
104-
return NextResponse.redirect(new URL(afterSignInUrl, request.url));
105-
}
106-
},
107-
108-
isSignedIn: async () => {
109-
return await asgardeoClient.isSignedIn(request);
110-
},
111-
112-
getUser: async () => {
113-
return await asgardeoClient.getUser(request);
114-
},
115-
116-
redirectToSignIn: (afterSignInUrl?: string) => {
117-
const signInUrl = afterSignInUrl || '/api/auth/asgardeo/signin';
118-
return NextResponse.redirect(new URL(signInUrl, request.url));
119-
},
120-
};
121-
122-
// Execute user-provided handler if present
123-
let handlerResponse: NextResponse | void;
124-
if (handler) {
125-
handlerResponse = await handler(auth, request);
126-
}
127-
128-
// If handler returned a response, use it
129-
if (handlerResponse) {
130-
return handlerResponse;
131-
}
132-
133-
// Otherwise, continue with default behavior
134-
const response = NextResponse.next();
135-
136-
// Add authentication context to response headers
137-
const isSignedIn = await asgardeoClient.isSignedIn(request);
138-
if (isSignedIn) {
139-
response.headers.set('x-asgardeo-authenticated', 'true');
140-
const user = await asgardeoClient.getUser(request);
141-
if (user?.sub) {
142-
response.headers.set('x-asgardeo-user-id', user.sub);
143-
}
144-
}
145-
146-
return response;
79+
// // Initialize client if not already done
80+
// if (!asgardeoClient.isInitialized && resolvedOptions) {
81+
// asgardeoClient.initialize(resolvedOptions);
82+
// }
83+
84+
// // Debug logging
85+
// if (resolvedOptions.debug) {
86+
// console.log(`[Asgardeo Middleware] Processing request: ${request.nextUrl.pathname}`);
87+
// }
88+
89+
// // Handle auth API routes automatically
90+
// if (request.nextUrl.pathname.startsWith('/api/auth/asgardeo')) {
91+
// if (resolvedOptions.debug) {
92+
// console.log(`[Asgardeo Middleware] Handling auth route: ${request.nextUrl.pathname}`);
93+
// }
94+
// return await asgardeoClient.handleAuthRequest(request);
95+
// }
96+
97+
// // Create auth object for the handler
98+
// const auth: AsgardeoAuth = {
99+
// protect: async (options?: {redirect?: string}) => {
100+
// const isSignedIn = await asgardeoClient.isSignedIn(request);
101+
// if (!isSignedIn) {
102+
// const afterSignInUrl = options?.redirect || '/api/auth/asgardeo/signin';
103+
// return NextResponse.redirect(new URL(afterSignInUrl, request.url));
104+
// }
105+
// },
106+
107+
// isSignedIn: async () => {
108+
// return await asgardeoClient.isSignedIn(request);
109+
// },
110+
111+
// getUser: async () => {
112+
// return await asgardeoClient.getUser(request);
113+
// },
114+
115+
// redirectToSignIn: (afterSignInUrl?: string) => {
116+
// const signInUrl = afterSignInUrl || '/api/auth/asgardeo/signin';
117+
// return NextResponse.redirect(new URL(signInUrl, request.url));
118+
// },
119+
// };
120+
121+
// // Execute user-provided handler if present
122+
// let handlerResponse: NextResponse | void;
123+
// if (handler) {
124+
// handlerResponse = await handler(auth, request);
125+
// }
126+
127+
// // If handler returned a response, use it
128+
// if (handlerResponse) {
129+
// return handlerResponse;
130+
// }
131+
132+
// // Otherwise, continue with default behavior
133+
// const response = NextResponse.next();
134+
135+
// // Add authentication context to response headers
136+
// const isSignedIn = await asgardeoClient.isSignedIn(request);
137+
// if (isSignedIn) {
138+
// response.headers.set('x-asgardeo-authenticated', 'true');
139+
// const user = await asgardeoClient.getUser(request);
140+
// if (user?.sub) {
141+
// response.headers.set('x-asgardeo-user-id', user.sub);
142+
// }
143+
// }
144+
145+
// return response;
146+
147+
return NextResponse.next();
147148
};
148149
};
149150

packages/nextjs/src/server/AsgardeoProvider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ const AsgardeoServerProvider: FC<PropsWithChildren<AsgardeoServerProviderProps>>
7979

8080
return (
8181
<AsgardeoClientProvider
82+
baseUrl={config.baseUrl}
8283
signIn={signInAction}
8384
signOut={signOutAction}
8485
signInUrl={configuration.signInUrl}
8586
preferences={config.preferences}
87+
clientId={config.clientId}
8688
>
8789
{children}
8890
</AsgardeoClientProvider>

0 commit comments

Comments
 (0)