TL;DR Use localStorage to communicate between windows and check it when the window is closed.
The OAUTH e2e process is as follows:
- FrontEnd(FE): user clicks on social login button
- FE: user is redirected to either a local page that redirects behind the scenes to the page of the social network
- Social Network (SN): prompts the user for login information and authorization of your app
- SN: redirects the user back to you app to a route that you provided with a response that has data about the user in it
Back End(BE): on that route you do the following:
check if everything was ok with the authorization of your app in the social network provider,
take the returned token, email, username and whatever else you need
query your database which usually gives you two options:
- create a new user, if there is none in your database
- login the user in your application, if you find the user in your database
redirect the user to a page in your app(homepage) with the state changed to logged in
Whenever you do an OAUTH login, you will need to setup 2 routes:
- [get] route 1: to trigger the communication with the social network provider
- [get] route 2: the route where the social network provider will redirect after the user authorizes your application
Example in adonisjs:
// route 1 - the redirect from your app to google
Route.get('/google/redirect', async (ctx) => {
const { default: GoogleController } = await import(
'App/Controllers/Http/GoogleController'
)
return new GoogleController().redirect(ctx);
}).middleware('guest');
// route 2 - the callback from google to your app
Route.get('/google/callback', async (ctx) => {
const { default: GoogleController } = await import(
'App/Controllers/Http/GoogleController'
)
return new GoogleController().callback(ctx);
}).middleware('guest');
However, a difficulty might appear if you decide to open 'route 1' in another window, as the callback will be returned in the same popup window and your user will be logged in in that window. And I hope we can agree that this is not a good UI.
Enter 'the third route'
You will need a third route, that will have the only purpose to render a blank page with some JavaScript code that will do the following:
set an item in the localStorage
close that window
You will redirect your app to this third route from the controller that handles route 2 after everything was fine and you've logged in the user.
// route 3 - the login success redirect
Route.get('/social-success-redirect', async ({ view }) => {
return view.render('auth/social-success-redirect')
})
The social-success-redirect template will basically be a blank html page with a script similar to this. Note this is a simplified version.
localStorage.setItem('socialLogin', true);
// you are in a window popup that you need to close
window.open(location, '_self').close();
And in the login page, you will have something like this:
const socialLoginBtn = document.getElementById('social-login');
const socialLoginStorageKey = 'socialLogin';
if (socialLoginBtn) {
socialLoginBtn.addEventListener('click', socialLogin)
}
async function socialLogin(event) {
event.preventDefault();
// wait for the window to get closed
const response = await socialLoginPromise();
// if it's closed by the user without the actual login, show an error
if (!response) {
return showSocialLoginError();
}
// if everything is okay, you redirect to the homepage and clear the storage key
window.location.href = '/';
clearLocalStorageKey(socialLoginStorageKey);
}
function socialLoginPromise() {
return new Promise(resolve => {
openPopupWindow('http://localhost:3333/google/redirect', resolve);
})
}
function openPopupWindow(url, promiseResolver) {
const windowFeatures = "height=600,width=600";
const newWindow = window.open(url, 'socialLogin', windowFeatures);
// important step: in an interval you check if the window is closed
// when it's closed, you resolve the promise with the local storage data
const intervalWindowCheck = setInterval(() => {
if (newWindow.closed) {
clearInterval(intervalWindowCheck);
// you get the key that should be set up in the window
const storageLoginStatus= window.localStorage.getItem(socialLoginStorageKey)
resolve(storageLoginStatus);
}
}, 1000);
if (window.focus) {
newWindow.focus()
}
}
Don't forget to do error handling.
Cheers!