Cant get appState from handleRedirectCallback

Hi,

i’m implementing autho-spa-js in a React + Typescript project.
Im gonna paste my code for you to have a look after the following questions…

  1. i’m trying to access a specific path (NOT the root path) in my application and i want after redirect to access appState in order to push history with react router to the path that the user wanted to be at and for some reason it’s undefined. const { appState } = await authClient.handleRedirectCallback(); appState is always undefined.

Thank you and here is my code:

import React, {Component, createContext} from 'react';
import createAuth0Client from '@auth0/auth0-spa-js';
import Spinner from "../components/Spinner";
import {IUser} from "../interfaces/Models.interface";
import { Result, Button } from 'antd';
import clearAllCookiesForDomain from "../utils/authenticationUtils";
import {IAuthContext} from "../interfaces/Contexts.interface";
import * as Moment from "moment-timezone/moment-timezone";
import IAppConfig from "../interfaces/AppConfig.interface";
import IServices from "../interfaces/services";
import Styles from './Style';
import Auth0Client from "@auth0/auth0-spa-js/dist/typings/Auth0Client";
import autobind from "autobind-decorator";
import Logo from 'src/res/logo-v2.svg';
import {withRouter, RouteComponentProps} from "react-router-dom";

export const AuthContext = createContext<IAuthContext>({} as IAuthContext);

interface IProps extends RouteComponentProps {
services: IServices;
configuration: IAppConfig;

}

interface IState {
authClient?: Auth0Client;
isLoading: boolean;
isAuthenticated: boolean;
user?: IUser;
error?: string;
}

class AuthProvider extends Component<IProps, IState> {
public constructor(props: IProps) {
    super(props);
    this.state = {
        isLoading: true,
        isAuthenticated: false,
        user: undefined,
        authClient: undefined,
    };
}

public static handleUnauthorizedResponse = (callback: (...p: any) => any, response:any) => {
    callback();
};

public async componentDidMount() {
    await this.initializeAuth0();
}

public async componentDidUpdate(prevProps: Readonly<IProps>, prevState: Readonly<IState>, snapshot?: any): Promise<void> {
    const { authClient, isLoading, isAuthenticated, user, error } = this.state;
    const { services: {userService}, location: {pathname, search} } = this.props;

    if (isLoading) {
        return;
    }

    if (error) {
        return;
    }
    if (!isLoading && !isAuthenticated) {
        this.clearAppCacheAndCookies();
        await authClient?.loginWithRedirect({
            appState: process.env.REACT_APP_AUTH0_REDIRECT_URI + pathname + search
        );
        return;
    }
    if (!user || !userService.getToken()) {
        await this.setUserAndTokenCache();
        return;
    }
}

private initializeAuth0 = async () => {
    const { services: { userService }, location: {search} } = this.props;
    const user: IUser | undefined = userService.getCachedUser();
    this.setState({user});
    const config: Auth0ClientOptions = {
        domain: `${process.env.REACT_APP_AUTH0_DOMAIN}`,
        client_id: `${process.env.REACT_APP_AUTH0_CLIENT_ID}`,
        redirect_uri: process.env.REACT_APP_AUTH0_REDIRECT_URI
    };
    const authClient = await createAuth0Client(config);
    this.setState({ authClient: authClient });
    AuthProvider.handleUnauthorizedResponse = AuthProvider.handleUnauthorizedResponse.bind(authClient, () => this.logout());
    if (search.includes('code=') || search.includes('error=')) {
        return await this.handleRedirectCallback();
    }
    const isAuthenticated = await authClient.isAuthenticated();
    this.setState({ isLoading: false, isAuthenticated});
};

private handleRedirectCallback = async () => {
    const { location: {pathname}, history } = this.props;
    const { authClient } = this.state;
    if (!authClient) {
        return;
    }
    this.setState({ isLoading: true });
    try {
        const location = {
            pathname,
        };
        const { appState } = await authClient.handleRedirectCallback();
        const targetUrl = appState?.targetUrl;
        targetUrl ? history.push(targetUrl) : history.replace(location);
        this.setState({ isAuthenticated: true });
    } catch(e) {
        this.setState({error: e.message});
    } finally {
        this.setState({ isLoading: false});
    }
};

private setUserAndTokenCache = async () => {
    const { services: { userService } } = this.props;
    await this.setToken();
    const user: IUser | undefined = await userService.get();
    if (user) {
        userService.setUserCache(user);
    }
    this.setState({user});
};

private setToken = async (): Promise<string | void> => {
    const { authClient } = this.state;
    const { configuration: {localStorageKeys}, services: { localStorageService } } = this.props;
    const tokenObject: IdToken | undefined = await authClient?.getIdTokenClaims();
    if (!tokenObject) {
        return this.logout();
    }
    const futureExpiryTimeAsUnixTimeMs = tokenObject.exp ? tokenObject.exp * 1000 : Moment.utc().add(24,"hours").valueOf();
    const nowAsMs = Moment.utc().valueOf();
    const ttl = futureExpiryTimeAsUnixTimeMs - nowAsMs;
    localStorageService.set(localStorageKeys.TOKEN, tokenObject.__raw, ttl);
    return tokenObject.__raw;
};

@autobind
private async logout(options?: LogoutOptions) {
    const { authClient } = this.state;
    this.clearAppCacheAndCookies();
    await authClient?.logout({ returnTo: process.env.REACT_APP_AUTH0_REDIRECT_URI, ...options });
};

private clearAppCacheAndCookies = () => {
    const { services: { userService, siteService } } = this.props;
    clearAllCookiesForDomain();
    siteService.clearCache();
    userService.clearCache();
};

public render() {
    const { authClient, isLoading, isAuthenticated, user, error } = this.state;
    const { children, services: { userService } } = this.props;

    if (error) {
        return (
            <div style={Styles.errorResultContainer}>
                <img style={Styles.errorResultLogo} src={Logo} alt={"logo"}/>
                <Result
                    status="500"
                    extra={
                        <>
                            <p style={Styles.resultSubtitle}>{error}</p>
                            <br/>
                            {isLoading ? <Spinner/> : <Button style={Styles.logoutButton} onClick={this.logout.bind(this, {})} type="primary">Log Out</Button>}
                        </>
                    }
                />
            </div>
        );
    }

    if (!user || !userService.getToken()) {
        return <Spinner/>
    }

    const configObject = {
        isLoading,
        isAuthenticated,
        user,
        loginWithRedirect: (options?: RedirectLoginOptions) => authClient?.loginWithRedirect(options),
        getTokenSilently: (options?: GetTokenSilentlyOptions) => authClient?.getTokenSilently(options),
        getIdTokenClaims: (options?: getIdTokenClaimsOptions) => authClient?.getIdTokenClaims(options),
        logout: (options?: LogoutOptions) => this.logout(options)
    };

    return <AuthContext.Provider value={configObject}>{children}</AuthContext.Provider>;
}
}

export default withRouter(AuthProvider);
2 Likes

Never mind i found the problem, loginWithRedirect takes options object and the targetUrl can be passed there amongst other state properties.

await authClient?.loginWithRedirect({
            appState: {
                targetUrl: pathname + search
            }
        });
5 Likes

thanks
that helped me !!!

Perfect! Thanks for sharing the solution with the rest of community!

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.