Search code examples
apireactjstwo-factor-authentication

Two factor registration with react and php api


What is the proper way to do two factor registration when dealing with api?

1) On frontend there is a form - email and phone - sending data to api method

2) I have an API that register user, by email and phone, generate code and send it over sms on phone

Question - what is the proper way to check sms code on front? (send code as response for register looking not so secure)


Solution

  • I think the flow should be (more or less):

    client:
    - user fills out form and hits submit
    - sends form data to backend
    - show field for 2FA code entry
    
    server:
    - handles form data, generates a 2FA code, and sends that code to the client via email and/or SMS
    - store that code in a DB (relational or not, doesn't really matter); you could even just store it whatever session manager you're using
    - map that code to the current session, or supply that code to the client to correlate the 2FA code to the client
    

    the user receives the code

    client:
    - user enters the code and hits submit
    - sends entered code to the server
    
    server:
    - using the session or the DB, validates the code
    - then, and only then, you auth the user
    

    You're right - never validate auth on the frontend.

    Your basic code should look like this on the frontend:

    class TwoFactorAuth extends React.Component {
        state = { stage: 1 };
    
        onSubmitStageOne = (data) => {
            // goes to /
            submit(data).then(() => {
                this.setState({ stage: 2 });
            });
        }
    
        onSubmitStageTwo = (data) => {
            // goes to /auth
            authenticate(data).then(() => {
                // success
            }).catch(() => {
                // wrong code, try again
            });
        }
    
        renderStageOne() {
            return (
                <form onSubmit={this.onSubmitStageOne}>
                    <input name="email" />
                    <input name="phone" />
                </form>
            )
        }
    
        renderStageTwo() {
            return (
                <form onSubmit={this.onSubmitStageTwo}>
                    <input name="code" />
                </form>
            )
        }
    
        render() {
            return this.state.stage === 1 ? 
                this.renderStageOne() : 
                this.renderStageTwo(); 
        }
    }
    

    something to note is that I'm using ES6 classes and ES7 class property initializers - you'll need babel if you want to copy/paste the code above.

    your backend routes would be similar to:

    router.post("/", (req, res) => {
        const code = utils.generate2FACode();
        request.session["code"] = code;
    
        utils.emailOrSMSUser(code);
    
        res.sendStatus(200);
    });
    
    router.post("/auth", (req, res) => {
        const code = req.session["code"];
        if (code === req.body.code) {
            req.session["signedIn"] = true; // simplified for demonstration
            res.sendStatus(200);
        } else {
            res.sendStatus(401);
        }
    });
    

    here I'm using express and express-session. I'll let you write generate2FACode and emailOrSMSUser.

    edit: whoops, you said PHP API in the title - similar logic, just with PHP instead of Node/Express.