ReactJS Authorization with Django API

ReactJS Authorization with Django API

User authorization is very basic requirement for most modern web applications. This article describes my experience in building my first live ReactJS application based on Django REST Api.

Django REST Api preparing

I started from integrating Django REST Framework and Django REST Auth packages into project with command:

 pip install django-rest-auth djangorestframework 

After packages were installed successfully I’ve updated INSTALLED_APPS list and REST_FRAMEWORK object in my django config:

INSTALLED_APPS = (
    ...,
    'rest_framework',
    'rest_framework.authtoken',
    ...,
    'rest_auth'
)
.....
REST_FRAMEWORK = {
     'DEFAULT_PERMISSION_CLASSES': (
         'rest_framework.permissions.IsAuthenticated',
     ),
     'DEFAULT_AUTHENTICATION_CLASSES': (
         'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication',
         'rest_framework.authentication.TokenAuthentication',
     ),
 }

My api has separate django application called ‘api’. So, next step was to add token retrieving api endpoint to the api app (file api/urls.py):

.....
from rest_framework.authtoken.views import obtain_auth_token
.....
urlpatterns = [
    path('token-auth/', obtain_auth_token),
    .....
]

Rest auth package contains DB migrations, next command was:

python manage.py migrate

Finally I could successfully test API part authorization with Postman tool:

Successful post request to api authorization endpoint.

What I’ve done by that? My goal in this part was to create an api endpoint which accepts POST request from a client (ReactJS is an external client for Django just like Postman) containing JSON object having fields: username and password. The endpoint is supposed to return JSON object having token info if credentials are correct and 403 HTTP error otherwise.

ReactJS client creation

I started building React JS app with creating a service which would contain all API requests ( just like model contains DB requests in MVC pattern ). I’ve created “services/apiservice.js” file and added ApiService class there:

export default class ApiService {
    login = async( { username, password } ) => {
        const requestOptions = {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ username:username, password:password })
        };
        const res = await fetch(`${config.get('apiUrl')}/token-auth/`, requestOptions);
        if (!res.ok)
        {
            throw new Error(`Could not fetch ${res.url}, received ${res.status}`);
        }
        return await res.json();
    };
}

As you can see I’ve added async login method there which returns a promise containing POST request to the api authorization endpoint.

The next step is to create login form. I follow ReactJS code recommendations and try to divide my app into many small reusable components.

I have created Form component containing form state, submit logic and display rules:

export default class Form extends Component
{

    state = {
        isSubmitting: false,
        failed:false,
        values:{},
    };

    onSubmit = ( event ) => {
        event.preventDefault();

        this.setState({isSubmitting:true});

        const {
            onError = () => {},
            onComplete = () => {}
        } = this.props;

        this.props.onSubmit(this.state.values)
            .then((data) => {
                this.setState({isSubmitting:false});
                this.setState({data:data});
                onComplete(data);
            })
            .catch((error) => {
                this.setState({
                    isSubmitting:false,
                    failed:true,
                });
                onError(error);
            });
    };

    onInputChange = (input, value) => {
        let values = this.state.values;
        values[input] = value;
        this.setState({values:values});
    };

    render(){

        const {submitLabel = 'Send'} = this.props;

        if ( this.state.isSubmitting )
        {
            return <div></div>;
        }

        return <form onSubmit={this.onSubmit}>
            {
                React.Children.map(this.props.children, (child) => {

                    const {id,value: initial_value} = child.props;
                    const current_value = this.state.values[id];

                    let value = "";
                    if ( typeof current_value !== "undefined" )
                    {
                        value = current_value;
                    }
                    else if ( typeof initial_value !== "undefined" )
                    {
                        value = initial_value;
                    }

                    return React.cloneElement(child, {
                        val:value,
                        onChange: (value)=>this.onInputChange(id,value),
                    });
                })
            }
            <button type="submit" className="btn btn-primary">{submitLabel}</button>
        </form>
    };
};

Input component, which wraps regular html input:

export default class Input extends Component
{

    onChange = (e) => {
        const val = e.target.value;
        this.props.onChange(val);
    };

    render(){

        const {id,type,label,val,placeholder='',description=''} = this.props;

        return <Fragment>
            <label htmlFor={id}>{label}</label>
            <input type={type} className="form-control" name={id} id={id} placeholder={placeholder} value={val} onChange={this.onChange}/>
            <small className="form-text text-muted">{description}</small>
        </Fragment>
    }
};

And, finally, FormLogin component which wraps Form I’ve created above.

const FormLogin = () => {

    const authService = useContext(AuthServiceContext);
    const eduenvService = useContext(EduenvServiceContext);

    const onComplete = ({token}) => {
        if ( typeof token !== "undefined" ) {
            authService.setToken(token);
        }
    };
    return <Form onSubmit="{eduenvService.login}" onComplete="{(data)=">{onComplete(data)}} submitLabel="Login">
        <Input type="text" label="Username" id="username" />
        <Input type="password" label="Password" id="password" />
    
};

How it works

Form component from above collects all data from children Input components in state.values object and pass it to onSubmit method. The method is defined in FormLogin component – eduenvService.login.

Finally eduenvService.login method pass all input data as body in auth request to the Django API end point, which returns token value which is saved in localStorage for further usage.

Author: vlad