-
Notifications
You must be signed in to change notification settings - Fork 40
Description
If the app developer doesn't provide their own 'select provider' UI, it would be helpful if calling login() without passing in an IDP would open a Select Provider popup.
There's two workflow options for this popup window that we have to decide between -- either 1) it serves as a select provider UI only, and it's the main app window that redirects for the OAuth dance. Or 2) The redirect happens in the popup window, which is more convenient for the app developer, but involves more moving parts to implement.
Recommendation: We can start with implementing Option 1, since that's the easiest, and discuss the next steps.
Option 1 - Popup is Select Provider Only, Main App Window Redirects
pro: Implementing this requires less moving parts.
con: More inconvenient for the app developer; the user can potentially lose their place in the page after the resulting redirect (for some single-page js apps, like Tabulator).
Workflow:
- main window:
login()is called with no IDP, popup window is opened, shows a list of IDPs (plus at least a textbox for user to enter their own domain or webid or preferred provider). - popup: User clicks on a provider (or enters a url in the textbox), popup window sends a
postMessage()event to the opener (main app window) with the url of the selected provider and closes itself. - main window: Main window (the auth client) receives the
providerSelectedevent, loads the RP client config for that IDP, constructs the/authorizeURL, and redirects itself to login. - main window: User logs in. When the IDP redirects the user to the post-login
redirect_uri(usually the same URI as the main app was), the app logic callsauthClient.currentSession()on page load, which results in the auth client parsing the callback uri and extracting the credentials from it, as usual.
Sample code snippets:
var selectProviderWindow
// providerSelectPopupSource is a string that contains the HTML + JS of the popup window
initEventListeners()
if (selectProviderWindow) {
// Popup has already been opened
selectProviderWindow.focus()
} else {
// Open a new Provider Select popup window
selectProviderWindow = window.open('',
'selectProviderWindow',
'menubar=no,resizable=yes,width=400,height=400'
)
selectProviderWindow.document.write(providerSelectPopupSource)
selectProviderWindow.document.close() // important
}
function initEventListeners() {
window.addEventListener('message', function (event) {
switch (event.data.event_type) {
case 'providerSelected':
dispatchProviderSelected(event.data.value) // provider uri
break
default:
console.error('unknown event type: ', event)
break
})
}In the popup window:
// opener is a browser global
function selectProvider (providerUri) {
console.log('Provider selected: ', providerUri)
var message = {
event_type: 'providerSelected',
value: providerUri
}
opener.postMessage(message, opener.window.location.origin)
// close yourself
window.close()
}Option 2 - Popup is Select Provider + Redirect to Login, Main Window Not Redirected
pro: More convenient for the app developer (user's place / workflow in the main app page is not interrupted).
con: More moving parts to implement (would require alteration to solid-server & config).
Workflow:
- main window: Decide on the callback
redirect_uriduring RP dynamic registration (see the callback uri discussion below). - main window:
login()is called with no IDP, popup window is opened, shows a list of IDPs (plus at least a textbox for user to enter their own domain or webid or preferred provider). - popup: User clicks on a provider (or enters a url in the textbox), popup window sends a
postMessage()event to the opener (main app window) with the url of the selected provider, and stays open. - main window: Main window (the auth client) receives the
providerSelectedevent, loads the RP client config for that IDP, constructs the/authorizeURL, and redirects the popup window to login. - popup: User logs in. When the IDP redirects the popup to the post-login
redirect_uri, some logic in theredirect_uripage must do apostMessageto theopener(main app page) with its own uri (containing hash fragments with credentials), and closes itself. (See the discussion below). - main window: Receives the
authCallbackevent from the popup window, with the callback uri as the event payload. Extracts the credentials from that uri, as usual (instead ofcurrentSessionderiving them from the main page's own current uri).
The tricky bit is step 5. What should be the redirect_uri that the RP client pre-registers, and asks to be redirected to during the authentication workflow? There's basically 3 options:
-
The rp client uses its own URI as a
redirect_uri(like it currently does). In order for this to work, though, the main app code needs to have a snippet like:// in the 'onload' window event if (opener) { opener.postMessage({ event_type: 'authCallback', value: window.location.href }, opener.window.location.origin) window.close() // close yourself }
as soon as the page loads (so that the full app doesn't load in the popup window).
-
The app developer includes a
callback.html(see below) as part of their web application, and tells the RP to register it as theredirect_uri. -
We include a
callback.htmlin the/common/folder of allnode-solid-serverinstallations, and advertise thecallback_uriendpoint either via aLinkrel header, or via a service config document. The RP discovers this uri, and includes it in theredirect_urislist during dynamic registration.
Sample callback.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
<script type="text/javascript">
window.addEventListener('load', function () {
var callbackUrl = window.location.href
var opener = window.opener
var pageOrigin = opener.location.origin
var message = {
event_type: 'authCallback',
value: callbackUrl
}
opener.postMessage(message, pageOrigin)
window.close()
})
</script>
</head>
<body>
<h1>Login successful</h1>
<p>Please close this window.</p>
</body>
</html>