react-oidc-context icon indicating copy to clipboard operation
react-oidc-context copied to clipboard

How to authenticate react routes using react-oidc-centext ?

Open goutam-das opened this issue 1 year ago • 4 comments

goutam-das avatar Feb 27 '23 13:02 goutam-das

Make use of the useAuth hook function, like described here https://github.com/authts/react-oidc-context#getting-started

pamapa avatar Mar 17 '23 12:03 pamapa

This is what I've come up with.

export function RequireAuth({ children, roles }: { children: JSX.Element, roles?: string[] }) {
    let auth: AuthContextProps = useAuth();
    let navigate: NavigateFunction = useNavigate();

    // Coped from https://github.com/authts/react-oidc-context
    // "automatically sign -in"
    useEffect(() => {
        if (!hasAuthParams() &&
            !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading) {
            auth.signinRedirect();
        }
    }, [auth.isAuthenticated, auth.activeNavigator, auth.isLoading, auth.signinRedirect]);

    useEffect(() => {
        if (!auth) { return; /* Possible bollocks initial value. */ }

        // I thought that maybe this might work, but it doesn't -- nothing is ever logged.
        auth.events.addAccessTokenExpired(() => { console.warn('UserManager.events.addAccessTokenExpired'); });
        auth.events.addAccessTokenExpiring(() => { console.warn('UserManager.events.addAccessTokenExpiring'); });
        auth.events.addSilentRenewError(() => { console.warn('UserManager.events.addSilentRenewError'); });
        auth.events.addUserLoaded(() => { console.warn('UserManager.events.addUserLoaded'); });
        auth.events.addUserSessionChanged(() => { console.warn('UserManager.events.addUserSessionChanged'); });
        auth.events.addUserSignedIn(() => { console.warn('UserManager.events.addUserSignedIn'); });
        auth.events.addUserSignedOut(() => { console.warn('UserManager.events.addUserSignedOut'); });
        auth.events.addUserUnloaded(() => { console.warn('UserManager.events.addUserUnloaded'); });

        // This breaks everything becauase it is blocked by the browser; no pre-flight check is done.
        //if (!auth.isAuthenticated) {
        //    console.warn(`-- RequireAuth signinRedirect user ${JSON.stringify(auth.user)}--`);
        //    auth.signinRedirect();
        //}

        if (roles && roles!.length > 0) {
            const routeRoles = roles.map((role) => role.toLowerCase());
            const userRoles: string[] = getUserRoles(auth.user?.id_token!).map((role) => role.toLowerCase());
            const role_authorised: boolean = routeRoles.some(role => userRoles.includes(role));

            if (!role_authorised) {
                navigate('forbidden');
                // Should we instead use the template to render ForbiddenComponent instead of {children}?
            }
        }
    }, [auth]);

    function getUserRoles(token: string): string[] {
        const decodedToken: PrestwoodIdentityAccessToken = jwtDecode<PrestwoodIdentityAccessToken>(token);
        let userRoles: string[] = decodedToken ? decodedToken.role : [];

        // BOOBYTRAP: If only one role then it could look like string instead of string[].
        return userRoles;
    }

    return (<Fragment>{auth.isAuthenticated ? children : (null)}</Fragment>);
}

Next is the problem of how to use roles.

It's easy in Angular but a nightmare in React -- switch to Angular if you can.

rwb196884 avatar Mar 25 '23 13:03 rwb196884

hi @rwb196884 what is hasAuthParams?

This is what I've come up with.

export function RequireAuth({ children, roles }: { children: JSX.Element, roles?: string[] }) {
    let auth: AuthContextProps = useAuth();
    let navigate: NavigateFunction = useNavigate();

    // Coped from https://github.com/authts/react-oidc-context
    // "automatically sign -in"
    useEffect(() => {
        if (!hasAuthParams() &&
            !auth.isAuthenticated && !auth.activeNavigator && !auth.isLoading) {
            auth.signinRedirect();
        }
    }, [auth.isAuthenticated, auth.activeNavigator, auth.isLoading, auth.signinRedirect]);

    useEffect(() => {
        if (!auth) { return; /* Possible bollocks initial value. */ }

        // I thought that maybe this might work, but it doesn't -- nothing is ever logged.
        auth.events.addAccessTokenExpired(() => { console.warn('UserManager.events.addAccessTokenExpired'); });
        auth.events.addAccessTokenExpiring(() => { console.warn('UserManager.events.addAccessTokenExpiring'); });
        auth.events.addSilentRenewError(() => { console.warn('UserManager.events.addSilentRenewError'); });
        auth.events.addUserLoaded(() => { console.warn('UserManager.events.addUserLoaded'); });
        auth.events.addUserSessionChanged(() => { console.warn('UserManager.events.addUserSessionChanged'); });
        auth.events.addUserSignedIn(() => { console.warn('UserManager.events.addUserSignedIn'); });
        auth.events.addUserSignedOut(() => { console.warn('UserManager.events.addUserSignedOut'); });
        auth.events.addUserUnloaded(() => { console.warn('UserManager.events.addUserUnloaded'); });

        // This breaks everything becauase it is blocked by the browser; no pre-flight check is done.
        //if (!auth.isAuthenticated) {
        //    console.warn(`-- RequireAuth signinRedirect user ${JSON.stringify(auth.user)}--`);
        //    auth.signinRedirect();
        //}

        if (roles && roles!.length > 0) {
            const routeRoles = roles.map((role) => role.toLowerCase());
            const userRoles: string[] = getUserRoles(auth.user?.id_token!).map((role) => role.toLowerCase());
            const role_authorised: boolean = routeRoles.some(role => userRoles.includes(role));

            if (!role_authorised) {
                navigate('forbidden');
                // Should we instead use the template to render ForbiddenComponent instead of {children}?
            }
        }
    }, [auth]);

    function getUserRoles(token: string): string[] {
        const decodedToken: PrestwoodIdentityAccessToken = jwtDecode<PrestwoodIdentityAccessToken>(token);
        let userRoles: string[] = decodedToken ? decodedToken.role : [];

        // BOOBYTRAP: If only one role then it could look like string instead of string[].
        return userRoles;
    }

    return (<Fragment>{auth.isAuthenticated ? children : (null)}</Fragment>);
}

Next is the problem of how to use roles.

It's easy in Angular but a nightmare in React -- switch to Angular if you can.

what is AuthParams?

ajmaln-tw avatar Dec 13 '23 05:12 ajmaln-tw

what is AuthParams?

See https://github.com/authts/react-oidc-context/blob/main/src/utils.ts

pamapa avatar Dec 13 '23 07:12 pamapa