0.0.8 • Published 5 years ago

reactxp-msaadalauth v0.0.8

Weekly downloads
2
License
MIT
Repository
github
Last release
5 years ago

ReactXP-MsaAdalAuth

GitHub license npm version npm downloads David David

An abstraction around ADAL plus a manual MSA implementation for unified authentication support for ReactXP apps.

Installation

npm install --save reactxp-msaadalauth

To use with react native platforms, install the appropriate ADAL library for your platform(s):

Sample Usage

Partial usage example stolen from a private app.

class AuthService implements Service {
    private _auth!: MsaAdalAuth;

    startup(): SyncTasks.Promise<void> {
        let adalRedirectUri: string;
        if (AppConfig.getPlatformType() === 'web') {
            adalRedirectUri = window.location.origin;
        } else if (AppConfig.getPlatformType() === 'windows') {
            // Windows uses an auto-generated redirect uri from your app's SID, so this is ignored.
            adalRedirectUri = '';
        } else {
            // Brokered auth
            adalRedirectUri = 'x-msauth-xyz://com.blah.myapp';
        }

        const adalConfig: MsaAdalAppConfig = {
            clientId: (AppConfig.getPlatformType() === 'web') ?
                AdalWebClientId :
                AdalNativeClientId,
            redirectUri: adalRedirectUri,
        };

        const msaConfig: MsaAdalAppConfig = {
            clientId: (AppConfig.getPlatformType() === 'web') ?
                MsaWebClientId :
                MsaNativeClientId,
            clientSecret: (AppConfig.getPlatformType() === 'web') ?
                undefined :
                MsaNativeClientSecret,
            redirectUri: (AppConfig.getPlatformType() === 'web') ?
                window.location.origin :
                '',
        };

        this._auth = new MsaAdalAuth(msaConfig, adalConfig, this._possibleLoginCallback);

        return this._auth.processStartupLogins();
    }

    loginNewUser(): SyncTasks.Promise<void> {
        const msaScopes = [GraphUserUrlScope];
        const adalResourceId = OutlookResourceId;

        // If we have any MSA-logged-in users, start with an AAD login.  On web, MSA will force you to log back out
        // again before letting you do an AAD login.  On native, it auto redirects over to AAD.
        const startWithAdal = _.some(this._users, u => u.enabled && u.isMsa);

        return this._auth.loginNewUser(msaScopes, adalResourceId, !startWithAdal);
    }

    private _possibleLoginCallback = (result: UserLoginResult) => {
        // Partial logins are from MSA, since AAD comes back with full info for us.
        if (result.partial) {
            // See if there's an existing user already for this.
            const existingUser = _.find(this._users, user => user.anchorMailbox === result.partial!!!.anchorMailbox);
            if (existingUser) {
                // See if there's an existing token on the user that matches scopes of the incoming token.
                let newTokenSet = _.clone(existingUser.accessTokens);
                const existingTokenIndex = _.findIndex(existingUser.accessTokens, token =>
                    _.isEqual(token.scopes, result.partial!!!.accessToken.scopes));
                if (existingTokenIndex !== -1) {
                    newTokenSet.splice(existingTokenIndex, 1, result.partial.accessToken);
                } else {
                    newTokenSet.push(result.partial.accessToken);
                }

                const updatedUser = _.extend({}, existingUser, { enabled: true, accessTokens: newTokenSet });
                this._users[updatedUser.userKey] = updatedUser;
                this._trackNewUser(updatedUser, true);
                return this._finishLoginOrUpdate();
            }

            // We have a partial login from MSA, but we need user info to finish it, so query for user info then
            // wrap back around to the login result.
            let promise: SyncTasks.Promise<void>;
            if (AppConfig.getPlatformType() === 'web') {
                promise = GraphClient.getMyInfo(result.partial.accessToken.token).then(info => {
                    const finalResult: UserLoginResult = {
                        full: {
                            userIdentifier: info.id,
                            displayName: info.displayName,
                            email: info.userPrincipalName,
                            anchorMailbox: result.partial!!!.anchorMailbox!!!,
                            refreshToken: result.partial!!!.refreshToken,
                            accessToken: result.partial!!!.accessToken!!!,
                            isMsa: true,
                        }
                    };
                    return this._possibleLoginCallback(finalResult);
                });
            } else {
                promise = ProfileClient.getMyInfo(result.partial.accessToken.token, result.partial.anchorMailbox).then(info => {
                    const finalResult: UserLoginResult = {
                        full: {
                            userIdentifier: result.partial!!!.anchorMailbox,
                            displayName: info.names[0].displayNameDefault,
                            email: info.accounts[0].userPrincipalName,
                            anchorMailbox: result.partial!!!.anchorMailbox,
                            refreshToken: result.partial!!!.refreshToken,
                            accessToken: result.partial!!!.accessToken,
                            isMsa: true,
                        }
                    };
                    return this._possibleLoginCallback(finalResult);
                });
            }
            return promise.catch(() => {
                // TODO: Do we get a username hint with the login so we can at least try again?
                return this.loginNewUser();
            });
        }

        if (result.full) {
            // Full and fresh login to store.
            const user = result.full;

            Instrumentation.log(LogTraceAreas.Auth, 'Logged in user: ' + JSON.stringify(user));

            const userKey = user.userIdentifier as UserKey;

            ServiceStateStore.internal_userLoggedIn(userKey);

            const lastUser = CurrentUsersStore.getUser(userKey);

            const newUser: User = {
                enabled: true,

                userKey,
                accessTokens: _.compact([user.accessToken]),
                refreshToken: user.refreshToken,
                anchorMailbox: user.anchorMailbox,
                fullName: user.displayName,
                email: user.email,
                isMsa: user.isMsa,
            };

            this._trackNewUser(newUser, true);

            return this._finishLoginOrUpdate();
        }

        return SyncTasks.Resolved();
    }

    getAuthToken(user: User, resourceHost: ResourceHost) {
        let applicableScope: string;
        let scopesToFetch: string[];

        if (user.isMsa) {
            applicableScope = GraphUserUrlScope;
            scopesToFetch = [applicableScope];
        } else {
            applicableScope = OutlookResourceId;
            scopesToFetch = [OutlookResourceId];
        }

        const usableToken = _.find(user.accessTokens, token => _.includes(token.scopes, applicableScope));
        if (usableToken) {
            return SyncTasks.Resolved(usableToken.token);
        }

        if (user.isMsa) {
            return this._auth.getMsaAuthToken(user.userKey, user.email, scopesToFetch, user.refreshToken);
        } else {
            return this._auth.getAdalAuthToken(user.userKey, user.email, applicableScope, user.refreshToken);
        }
    }
}
0.0.8

5 years ago

0.0.7

6 years ago

0.0.6

6 years ago

0.0.5

6 years ago

0.0.4

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago