Chain a social login from your website to your PWA

Image for post
Image for post

Great news, after having open-sourced my mobile and progressive web app Fluster last week (previous story), I follow up my move by releasing today my website on GitHub 🎉

Beside maybe the fact that this website is build with Angular, run as a server-side rendered app (SSR) and contains a simple blog based on markdown files, it looks probably pretty common. But, if you give it a second chance, you might notice, I hope, something interesting: this website offers its users the ability to perform a login with their social accounts, Google or Facebook, and to jump afterwards directly to my progressive web app 😋

Social login flows with a PWA

The idea beside these two flows, is to redirect the users, after they successfully logged themselves in their social accounts, to our application with temporary tokens in order to to validate and to complete the process with our backend.

Chaining a social login from a website to a PWA

Website

<button (click)="googleLogin()">Log me in with Google</button>

Then we implement the function googleLogin() which does the following:

  • It generates a random state to identify the login flow
  • Writes this state in a cookie (which should be available across our subdomains)
  • It finally redirects the user to her/his social account
constructor(@Inject(DOCUMENT) private document: Document) {

}

googleLogin() {

const state = this.generateRandomString(16);
const url = 'https://accounts.google.com/o/oauth2/v2/auth?';
const webClientId = 'my-google-web-client-id';
// The redirect url to be called after a successful login
const pwaUrl = 'https://m.mydomain.com/';
// First we save the state in a cookie
this.writeCookieState(state, true).then(() => {
// Then we build our social login url
const googleUrl: string = url + 'client_id='
+ webClientId +
'&response_type=code&scope=openid%20profile%20email&redirect_uri='
+ encodeURIComponent(pwaUrl)
+ '&nonce=' + state + '&state=' + state;
// And we redirect the user to his google account
this.document.location.href = googleUrl;
});

private writeCookieState(state: string, googleAuth: boolean)
 : Promise<{}> {

return new Promise((resolve) => {
// Note here the specific domain beginning with a point// That’s one of the key in order to be able to read the cookie from the subdomain
const baseDomain = '.mydomain.com’;
const expireAfter: Date =
moment(new Date()).add(5, 'm’).toDate();

this.document.cookie = 'Our_state={"state":"' + state + '", "googleAuth": ' + googleAuth + '}; expires=' + expireAfter + '; domain=' + baseDomain + '; path=/’;

resolve();
});

}
generateRandomString(length: number): string {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}

return text;
}
}

Good, nothing much to do in our website. We reached the point where we saved the state in a cookie and we redirected the user to her/his social account. Once successfully logged in, she/he will be redirected again but this time to our other application because we provided as parameter a redirection URL, not to our website, but to our PWA.

Progressive Web App

  1. The redirection to our PWA will happens with an url containing a code and a state as parameters. To catch these information, we could intercept them in app.component.ts and save them in a provider:
ngOnInit() {
const state: string = this.platform.getQueryParam('state');
const code: string = this.platform.getQueryParam('code');

this.loginService.setState(state);
this.loginService.setCode(code);
}

2. Once our PWA is fully loaded, we could start automatically the login process. Before displaying some codes, here a summary of what we are going to do:

  • First we will check if a state and a code have been read and added to our provider
  • Then we will try to retrieve the state we previously saved in the cross-domain cookie
  • Finally we will compare both states to validate the flow, respectively to validate the fact that the user who tries to log in is effectively the user who wants to log in
constructor(@Inject(DOCUMENT) private document: Document) {

}
private ionViewWillEnter() {
const state: string = this.loginService.getState();
const code: string = this.loginService.getCode();

if (state && code) {
const cookieState = this.getPwaLoginStateCookie();

if (cookieState && state === cookieState.state) {
// All good, we could do the login
this.doPwaLoginAndNavigate(cookiePwaLoginState, code);
}
}
}
private getPwaLoginStateCookie(): any {
if (!this.document.cookie) {
return null;
}

const cookies: RegExpExecArray =
RegExp('Our_state' + '[^;]+').exec(this.document.cookie);

if (!cookies) {
return null;
}
const cookie: string = decodeURIComponent(!!cookies ? cookies.toString().replace(/^[^=]+./, '') : '');

if (!cookie || cookie.indexOf('state') === -1) {
return null;
}

return JSON.parse(cookie);
}

Voilà, we were able to chain a login flow from our website to our PWA 😇

Cherry on the cake 🍒🎂

To infinity and beyond 🚀

David

Written by

Freelancer by day | Creator of DeckDeckGo by night | Organizer of the Ionic and IndieHackers Zürich Meetup

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store