Adding custom page without authentication in react-admin
Asked Answered
A

5

8

I want to add a signup page to my react-admin web portal. Since react-admin do not provide a signup page, I created a custom page and added it to custom routes:

customRoutes.js

import React from 'react';
import { Route } from 'react-router-dom';
import SignupForm from './signupForm';

export default [
    <Route path="/signup" component={SignupForm} noLayout/>,
];

The problem is that I am only able to open the page at /signup when a user is already signed in. Otherwise I am automatically redirected to /login route.

How to disable authentication for custom routes? Is there some attribute which <Route> accepts or something to do with the dataProvider.js?

EDIT:

Adding the representative code for my signupForm.js:

import React from 'react';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import { fetchUtils } from 'react-admin';
import { ApiUrl } from './config';

class SignupForm extends React.Component {
    constructor() {
        super();
        this.state = {
            fields: {
                username: '',
                password: ''
            }
    }


    handleChange = name => event => {
        let fields = this.state.fields;
        fields[name] = event.target.value;

        this.setState({
            fields: fields,
        });
    };

    handleSubmit = (event) => {
        // Prevent default
        event.preventDefault();

        if (this.handleValidation()) {
            let body = JSON.parse(JSON.stringify(this.state.fields));

            let url = ApiUrl + '/api/user/create';
            let options = {}
            options.headers = new Headers({ Accept: 'application/json' });
            options.method = 'POST'
            options.body = JSON.stringify(body);
            fetchUtils.fetchJson(url, options)
                .then(data => {
                    alert(data.json.message);
                    this.props.history.push('/login')
                })
                .catch((err, ...rest) => {
                    console.log(err.status, err.message);
                    alert(err.message);
                });
        }
    }

    render() {
        const { classes } = this.props;

        const actions = [
            <Button
                type="submit"
                label="Submit"
                color='primary'
                variant="flat"
            >Submit</Button>,
        ];

        return (
            <Dialog
                open={true}
                style={{
                    textAlign: "center",
                }}
                onClose={this.handleClose}
                classes={{ paper: classes.dialogPaper }}
            >
                <DialogTitle>Create an Account</DialogTitle>
                <form className={classes.container} noValidate autoComplete="off" onSubmit={this.handleSubmit}>
                    <TextField
                        required
                        id="username"
                        label="Username"
                        value={this.state.fields["username"]}
                        onChange={this.handleChange('username')}
                    />
                    <br />
                    <TextField
                        required
                        id="password"
                        label="Password"
                        value={this.state.fields["password"]}
                        onChange={this.handleChange('password')}
                        type="password"
                    />

                    <div style={{ textAlign: 'right', padding: 40 }}>
                        {actions}
                    </div>
                </form>
            </Dialog>
        );
    }
}

export default withStyles(styles)(SignupForm);
Archy answered 25/11, 2018 at 18:9 Comment(4)
What does your code for SignupForm look like?Killen
@Kosch added shortened representative code for my signup.jsArchy
I am new to react-admin so I don't know the answer. But I wonder if you can use the authorization hooks. So any one who is not authorized can get the "guest" permission, and perhaps you can use that to show/hide parts of your app. marmelab.com/react-admin/Authorization.htmlZabrina
@Zabrina yes it worked on similar lines, I have described in detail in my answer.Archy
A
7

The problem was that on request to /signup, react-admin was calling the authProvider with type AUTH_GET_PERMISSIONS whose code was:

if (type === AUTH_GET_PERMISSIONS) {
    const role = localStorage.getItem('role');
    return role ? Promise.resolve(role) : Promise.reject();
}

Since the user was not logged in so localStorage.role was never initialised.

Changed it to:

if (type === AUTH_GET_PERMISSIONS) {
    const role = localStorage.getItem('role');
    return role ? Promise.resolve(role) : Promise.resolve('guest');
}
Archy answered 5/12, 2018 at 16:47 Comment(1)
could you elaborate on this? I'm trying to implement the same functionality and your answer does not give enough info on how to do itMicrodont
S
1

React-admin V3 version, change authProvider to return 'guest' role by default:

authProvider.js

  ...,
  getPermissions: () => {
    const role = localStorage.getItem('permissions')
    return role ? Promise.resolve(role) : Promise.resolve('guest') // default 'guest'
  },
  ...

Now your customRoutes pages will no longer redirect to /login, and you can use the usePermissions hook to check the role in your custom page.

Spiroid answered 29/4, 2021 at 6:10 Comment(0)
T
0

You can make an enum-like object PublicRoutes with all unauthenticated routes in your customRoutes.js file like this:

customRoutes.js


export const PublicRoutes = {
    SIGNUP: "/signup",
}

export const customRoutes = [
     <Route path={PublicRoutes.SIGNUP} component={SignupForm} noLayout/>
]

Then in your authProvider import history or router object (created in your Admin component), to get access to current location pathname.

Next, make a function expression isPublicRoute, which will check if the current route can be served without authentication. Add this check on top of AUTH_GET_PERMISSIONS and optionally AUTH_CHECK (if you have e.g. JWT resolver here).

For AUTH_GET_PERMISSIONS return Promise.resolve() as we have permissions to public routes. For AUTH_CHECK return Promise.reject() as we don't need authorization here (e.g. fetch or resolve JWT).

authProvider.js


import {PublicRoutes} from "./customRoutes";

...

const isPublicRoute = (pathname) => Object.values(PublicRoutes).some(route => route === pathname);

...

const {pathname} = router.location;

if (type === AUTH_GET_PERMISSIONS) {
     // has to be on top 
     if(isPublicRoute(pathname)) return Promise.resolve();
     ...
}

if (type === AUTH_CHECK) {
     // has to be on top        
     if(isPublicRoute(pathname)) return Promise.reject()
     ...
}
Tangy answered 8/7, 2021 at 12:47 Comment(0)
I
0

Inside your authProvider you should have a method called checkAuth that returns Promise.resolve() if the window.location.pathname is a public route.

const publicRoutes = [
    '/appointment',
]

const authProvider: AuthProvider = {
    login: ({ username, password }) => {
    },
    checkAuth: () => {

        if (publicRoutes.includes(window.location.pathname)) {
            return Promise.resolve()
        }

        // your authentication endpoint / system
        // ...
    },
    getPermissions: () => {
    },
    logout: () => {
    },
    checkError: (error) => {
    },
    getIdentity: () => {
    },
};
Idaline answered 22/2, 2022 at 17:24 Comment(0)
S
0

in my case with react-admin 3.12.5 i need do this:

  1. Add noLayout for Route:

     const ForgotPassword = props => {
         return <div>Forgot Password View</div>;
     };
    
     const customRoutes = [
         <Route exact path="/forgot-password"
                component={ForgotPassword}
                noLayout/>,
     ];
    
    
     const App = (props) => {
         return (
             <Admin
                 theme={theme}
                 customRoutes={customRoutes}
                 authProvider={authProvider}
                 dataProvider={dataProvider}
                 i18nProvider={i18nProvider}
                 loginPage={MyLoginPage}
                 catchAll={() => <Redirect to="/"/>}
             >
                 {resourcesByPermissions}
             </Admin>
         )
     };
    
  2. Go to http://localhost:3000/#/forgot-password, need add /# for href

                 <a href="/#/forgot-password">Forgot-password?</a>
    
Snailpaced answered 26/12, 2022 at 16:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.