Skip to content

Commit

Permalink
Merge pull request AzureAD#2072 from AndrewCraswell/andcra/msal-react-pr
Browse files Browse the repository at this point in the history
Merge previous PR changes and add token story
  • Loading branch information
tnorling authored Sep 28, 2020
2 parents df53477 + 95776d1 commit 7af6f98
Show file tree
Hide file tree
Showing 22 changed files with 919 additions and 58 deletions.
1 change: 1 addition & 0 deletions experimental/msal-react/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
src/index.ts
2 changes: 1 addition & 1 deletion experimental/msal-react/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
stories: ['../stories/**/*.stories.(ts|tsx)'],
addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-docs'],
addons: ['@storybook/addon-actions', '@storybook/addon-links', '@storybook/addon-docs', '@storybook/addon-storysource'],
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.(ts|tsx)$/,
Expand Down
2 changes: 1 addition & 1 deletion experimental/msal-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions experimental/msal-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,8 @@
"tsdx": "^0.13.2",
"tslib": "^2.0.0",
"typescript": "^3.9.7"
},
"dependencies": {
"@storybook/addon-storysource": "^5.3.19"
}
}
74 changes: 74 additions & 0 deletions experimental/msal-react/src/MsalAuthentication.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { AuthenticationResult } from '@azure/msal-browser';
import React, { useState, useCallback, useEffect, useMemo } from 'react';

import { IMsalContext } from './MsalContext';
import { useMsal } from './MsalProvider';
import { getChildrenOrFunction, defaultLoginHandler } from './utilities';
import { useIsAuthenticated } from './useIsAuthenticated';

export interface IMsalAuthenticationProps {
username?: string;
loginHandler?: (context: IMsalContext) => Promise<AuthenticationResult>;
}

type MsalAuthenticationResult = {
error: Error | null;
msal: IMsalContext;
};

// TODO: Add optional argument for the `request` object?
export function useMsalAuthentication(
args: IMsalAuthenticationProps = {}
): MsalAuthenticationResult {
const { username, loginHandler = defaultLoginHandler } = args;
const msal = useMsal();
const isAuthenticated = useIsAuthenticated(username);

const [error, setError] = useState<Error | null>(null);

// TODO: How are we passing errors down?
const login = useCallback(() => {
// TODO: This is error prone because it asynchronously sets state, but the component may be unmounted before the process completes.
// Additionally, other authentication components or hooks won't have access to the errors.
// May be better to lift this state into the the MsalProvider
return loginHandler(msal).catch(error => {
setError(error);
});
}, [msal, loginHandler]);

useEffect(() => {
// TODO: What if there is an error? How do errors get cleared?
// TODO: What if user cancels the flow?
if (!isAuthenticated) {
login();
}
// TODO: the `login` function needs to be added to the deps array.
// Howevever, when it's added it will cause a double login issue because we're not
// currently tracking when an existing login is InProgress
}, [isAuthenticated]);

return useMemo(
() => ({
error,
msal,
}),
[error, msal]
);
}

export const MsalAuthentication: React.FunctionComponent<IMsalAuthenticationProps> = props => {
const { username, loginHandler, children } = props;
const { msal } = useMsalAuthentication({ username, loginHandler });
const isAuthenticated = useIsAuthenticated(username);

// TODO: What if the user authentiction is InProgress? How will user show a loading state?
if (isAuthenticated) {
return (
<React.Fragment>
{getChildrenOrFunction(children, msal)}
</React.Fragment>
);
}

return null;
};
5 changes: 3 additions & 2 deletions experimental/msal-react/src/MsalContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import * as React from 'react';
import { IPublicClientApplication, AccountInfo } from '@azure/msal-browser';

export type MsalState = {
type MsalState = {
accounts: AccountInfo[];
};

Expand All @@ -24,6 +24,7 @@ const defaultMsalContext: IMsalContext = {
return Promise.reject();
},
getAllAccounts: () => {
debugger;
return [];
},
getAccountByUsername: () => {
Expand Down
14 changes: 10 additions & 4 deletions experimental/msal-react/src/MsalProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useContext } from 'react';
import {
IPublicClientApplication,
AccountInfo,
Expand All @@ -18,11 +19,13 @@ export const MsalProvider: React.FunctionComponent<MsalProviderProps> = ({
}) => {
// State hook to store accounts
const [accounts, setAccounts] = React.useState<AccountInfo[]>(
// TODO: Remove the `|| []` hack when PR is finally merged to msal/browser
instance.getAllAccounts() || []
);

// Callback to update accounts after MSAL APIs are invoked
const updateContextState = React.useCallback(() => {
// TODO: Remove the `|| []` hack when PR is finally merged to msal/browser
setAccounts(instance.getAllAccounts() || []);
}, [instance]);

Expand Down Expand Up @@ -76,18 +79,21 @@ export const MsalProvider: React.FunctionComponent<MsalProviderProps> = ({
}, [instance, updateContextState]);

// Memoized context value
const contextValue = React.useMemo<IMsalContext>(() => {
return {
const contextValue = React.useMemo<IMsalContext>(
() => ({
instance: wrappedInstance,
state: {
accounts,
},
};
}, [wrappedInstance, accounts]);
}),
[wrappedInstance, accounts]
);

return (
<MsalContext.Provider value={contextValue}>
{children}
</MsalContext.Provider>
);
};

export const useMsal = () => useContext(MsalContext);
Loading

0 comments on commit 7af6f98

Please sign in to comment.