How to chain Http calls for 2 factor authentication getting user input between calls

Hello,

I’m learning Ionic 2 since about a week and I had done quite a good work following the documentations and tutorials. In a week I learnt completely new concepts like rxjs, observables etc.

I stuck at the moment implementing a client app to a backend service which requires 2-factor-authentication. The user first enters his username and password. If successful, then an SMS is sent to the user from the backend, which he must enter for the second step of the authentication.

The backend uses classical cookie based sessions and I’ve successfully achieved to first step of the authentication and stuck at implementing the second step.

I’ve the following code for the first step of the authentication:

export class LoginPage {
    // Credentials are coming from the view in the real implementation
    private credentials = {username: 'john.doe', password: 'MyPass123!'}
    private formUrl = 'https://example.com/login'

    // Constructor has all other necessary dependencies injected
    constructor(private http: Http) {}

    // This method is called from the view after user sends his credentials
    public login() {
        // Get request to the form to parse the CSRF token.
        this.http.get(this.formUrl)
            .map(resp => this.addCsrfToken(credentials, resp.text())) 
            .mergeMap(params => this.http.post(this.formUrl, params)
            .map(resp => this.checkLoginResponse(resp.text()))
            // ??? What to do here to start the 2nd step of the authentication
    }

    private addCsrfToken(credentials, html) {
        // Parse the html and add the CSRF token to the credentials
        // In the real implementation a URLSearchParams() object is created
        credentials.token = 'ParsedCsrfTokenFromHtmlForm';
        return credentials;
    }

    private checkLoginResponse(html) {
        // Parse the html to check if the first step was successfull
        return html.indexOf('Success.') !== -1; // Simplified for brevity
    }
}

I want to now prompt the user to enter a PIN number if the login response was true and send the the user input to the backend and check the response again. But I cannot figure it out how to go on.

I’ll be very thankful for any help or recommendation.

Thanks in advance!
Elin

Somebody is going to have to subscribe to the work being done in login(). That could be either at the end of login() itself (where your ??? is), or you could add a return at the beginning of login() and subscribe wherever login() is being called from.

Inside that subscription, you can pop a modal and do whatever else is needed for the second stage.

@rapropos Thanks for the quick reply! Showing a modal sounds reasonable.

I’ll experiment with it tomorrow with a fresh mind and post it here. Thanks!

I got it working and wanted to share the solution here. I didn’t use modals since it would be more challenging for me to pass the entered pin or the response from the backend to the main login process back. Instead I’ve used two forms in Login-Page and showed only one of the on each step.

My working login page component looks like as follows:

login.html

<ion-content>
    <form (submit)="login()" *ngIf="!pinRequired">
        <ion-list>
            <ion-item>
                <ion-label>Username</ion-label>
                <ion-input [(ngModel)]="credentials.username" type="text" name="username"></ion-input>
            </ion-item>
            <ion-item>
                <ion-label>Password</ion-label>
                <ion-input [(ngModel)]="credentials.password" type="password" name="password"></ion-input>
            </ion-item>
            <ion-item>
                <button ion-button>Login</button>
            </ion-item>
        </ion-list>
    </form>
    <form *ngIf="pinRequired" (submit)="sendSmsPin()">
        <ion-list>
            <ion-item>
                <ion-label>SMS PIN</ion-label>
                <ion-input [(ngModel)]="pin" type="password" name="pin"></ion-input>
            </ion-item>
            <ion-item>
                <button ion-button>Send</button>
            </ion-item>
        </ion-list>
    </form>
</ion-content>

login.ts

import {Component} from '@angular/core';
import {Http, RequestOptions, URLSearchParams} from '@angular/http';

import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';

@Component({
    selector: 'page-login',
    templateUrl: 'login.html'
})

export class LoginPage {
    private credentials: { username: string, password: string } = {
        username: '',
        password: ''
    };

    private pinRequired = false;
    private pin: string;

    private formUrl = 'https://example.com/login'

    constructor(public http: Http) {}

    public login() {
        this.http.get(this.formUrl)
            .map(resp => this.addCsrfToken(this.credentials, resp.text()))
            .mergeMap(params => this.http.post(this.formUrl, params))
            .map(resp => this.checkLoginResponse(resp.text()))
            .subscribe(status => {
                this.pinRequired = status;
            });
    }

    public sendSmsPin() {
        // In the real implementation a URLSearchParams object is created for POST data.
        this.http.post(this.formUrl, this.pin)
            .map(resp => this.checkLoginResponse(resp.text()))
            .subscribe(status => {
                if (status) {
                    // User is successfully logged in.
                }
                else {
                    // Pin was not correct.
                }
            });
    }

    private addCsrfToken(credentials, html) {
        // Parse the html and add the CSRF token to the credentials
        // In the real implementation a URLSearchParams object is created
        credentials.token = 'ParsedCsrfTokenFromHtmlForm';
        return credentials;
    }

    private checkLoginResponse(html) {
        // Parse the html to check if the first step was successfull
        return html.indexOf('Success.') !== -1; // Simplified for brevity
    }
}

I’ve tried to simplify the real implementation. I have actually used a provider for the HTTP requests to the backend, a provider for the Backend URLs, and used some UX utilities like LoadingController and ToastController. For this post I’ve put all in a component and removed the UX parts from the code.

I hope this will help someone.

1 Like