Skip to content

Commit

Permalink
Add hook support for authenticating using Password Realm grant
Browse files Browse the repository at this point in the history
  • Loading branch information
kailash-b committed Jan 24, 2025
1 parent 1510b11 commit 5ea505d
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 2 deletions.
60 changes: 60 additions & 0 deletions src/hooks/__tests__/use-auth0.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ const mockAuth0 = {
loginWithOTP: jest.fn().mockResolvedValue(mockCredentials),
loginWithRecoveryCode: jest.fn().mockResolvedValue(mockCredentials),
hasValidCredentials: jest.fn().mockResolvedValue(),
loginWithRecoveryCode: jest.fn().mockResolvedValue(mockCredentials),
passwordRealm: jest.fn().mockResolvedValue(mockCredentials),
},
credentialsManager: {
getCredentials: jest.fn().mockResolvedValue(mockCredentials),
Expand Down Expand Up @@ -781,6 +783,64 @@ describe('The useAuth0 hook', () => {
expect(result.current.error).toBe(mockAuthError);
});

it('can authorize with password realm, passing through all parameters', async () => {
const { result } = renderHook(() => useAuth0(), {
wrapper,
});

let promise = result.current.authorizeWithPasswordRealm({
username: '[email protected]',
password: 'random-password',
realm: 'react-native-sample-app',
});

await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(mockAuth0.auth.passwordRealm).toHaveBeenCalledWith({
username: '[email protected]',
password: 'random-password',
realm: 'react-native-sample-app',
});

let credentials;
await act(async () => {
credentials = await promise;
});
expect(credentials).toEqual({
idToken:
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cHM6Ly9hdXRoMC5jb20iLCJhdWQiOiJjbGllbnQxMjMiLCJuYW1lIjoiVGVzdCBVc2VyIiwiZmFtaWx5X25hbWUiOiJVc2VyIiwicGljdHVyZSI6Imh0dHBzOi8vaW1hZ2VzL3BpYy5wbmcifQ==.c2lnbmF0dXJl',
accessToken: 'ACCESS TOKEN',
});
});

it('sets the user prop after successful authentication', async () => {
const { result } = renderHook(() => useAuth0(), {
wrapper,
});

result.current.authorizeWithPasswordRealm();
await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(result.current.user).toMatchObject({
name: 'Test User',
familyName: 'User',
picture: 'https://images/pic.png',
});
});

it('does not set user prop when authentication fails', async () => {
mockAuth0.auth.passwordRealm.mockRejectedValue(mockAuthError);
const { result } = renderHook(() => useAuth0(), {
wrapper,
});

result.current.authorizeWithPasswordRealm();
await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(result.current.user).toBeNull();
expect(result.current.error).toBe(mockAuthError);
});

it('can clear the session', async () => {
const { result } = renderHook(() => useAuth0(), {
wrapper,
Expand Down
8 changes: 8 additions & 0 deletions src/hooks/auth0-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
WebAuthorizeParameters,
PasswordlessWithSMSOptions,
ClearSessionOptions,
PasswordRealmOptions,
} from '../types';

export interface Auth0ContextInterface<TUser extends User = User>
Expand Down Expand Up @@ -105,6 +106,12 @@ export interface Auth0ContextInterface<TUser extends User = User>
* Clears the user's credentials without clearing their web session and logs them out.
*/
clearCredentials: () => Promise<void>;
/**
* Authorize user with credentials using the Password Realm Grant. See {@link Auth#passwordRealm}
*/
authorizeWithPasswordRealm: (
parameters: PasswordRealmOptions
) => Promise<Credentials | undefined>;
}

export interface AuthState<TUser extends User = User> {
Expand Down Expand Up @@ -143,6 +150,7 @@ const initialContext = {
clearSession: stub,
getCredentials: stub,
clearCredentials: stub,
authorizeWithPasswordRealm: stub,
};

const Auth0Context = createContext<Auth0ContextInterface>(initialContext);
Expand Down
19 changes: 19 additions & 0 deletions src/hooks/auth0-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
User,
WebAuthorizeOptions,
WebAuthorizeParameters,
PasswordRealmOptions,
} from '../types';
import { CustomJwtPayload } from '../internal-types';
import { convertUser } from '../utils/userConversion';
Expand Down Expand Up @@ -301,6 +302,22 @@ const Auth0Provider = ({
[client]
);

const authorizeWithPasswordRealm = useCallback(
async (parameters: PasswordRealmOptions) => {
try {
const credentials = await client.auth.passwordRealm(parameters);
const user = getIdTokenProfileClaims(credentials.idToken);
await client.credentialsManager.saveCredentials(credentials);
dispatch({ type: 'LOGIN_COMPLETE', user });
return credentials;
} catch (error) {
dispatch({ type: 'ERROR', error });
return;
}
},
[client]
);

const hasValidCredentials = useCallback(
async (minTtl: number = 0) => {
return await client.credentialsManager.hasValidCredentials(minTtl);
Expand Down Expand Up @@ -334,6 +351,7 @@ const Auth0Provider = ({
clearSession,
getCredentials,
clearCredentials,
authorizeWithPasswordRealm,
}),
[
state,
Expand All @@ -350,6 +368,7 @@ const Auth0Provider = ({
clearSession,
getCredentials,
clearCredentials,
authorizeWithPasswordRealm,
]
);

Expand Down
5 changes: 3 additions & 2 deletions src/hooks/use-auth0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import Auth0Context, { Auth0ContextInterface } from './auth0-context';
* clearSession,
* getCredentials,
* clearCredentials,
* requireLocalAuthentication
* requireLocalAuthentication,
* authorizeWithPasswordRealm,
* } = useAuth0();
* ```
*
*
* Refer to {@link Auth0ContextInterface} on how to use the above methods.
*/
const useAuth0 = () => useContext(Auth0Context);
Expand Down

0 comments on commit 5ea505d

Please sign in to comment.