Passwordless authentication for your website in 5 minutes!

Arnaud Dagnelies - Jan 13 '23 - - Dev Community

Passwordless authentication using your phone's fingerprint sensor, or face recognition using your webcam is now possible in the browser thanks to the WebAuthn protocol. It is not only more comfortable for users but also more secure since it is two-factor authentication in a single step.

In this demo, we will use the free Passwordless.ID API. This is a public identity provider, and it will take care of the heavy lifting for us. It's free and you don't even need to register an account or download anything to use it.

The following picture describes what we are about to do.

Our demo website will simply redirect to passwordless.id to authenticate "passwordlessly" (no e-mail required either) and authorize reading the profile, then return to the original demo page.

Thanks to it, we will obtain the user's avatar for that demo and display it, that's it! What is also obtained, besides the user's profile, is a signed Json Web Token. This token can later be sent to your APIs as proof of the user's identity. More on that later.


Live Demo

Please note that the authentication will be blocked within the IFrame for security reasons, click on the upper right to open the CodeSandbox in its own tab!

If somehow the CodeSandbox is not shown properly or does not work, you can also see the live demo here and the source code here.

The Passwordless.ID API follows the OAuth2 / OpenID standards, just like google or facebook logins. It is nevertheless simpler and easier to integrate with since it is a public API, not requiring any kind of special authorization.


Step by step tutorial

Although it is compatible with most other "Sign in with..." client libraries as a generic OpenID provider, the easiest way to integrate with it though is to use the @passwordless-id/connect library.

Let us go through the code, starting with the "Sign In" button.

<button id="sign-in" hidden class="btn btn-light" onclick="app.signIn()">Sign In</button>
Enter fullscreen mode Exit fullscreen mode

Now, this is very basic, and depending on the frontend framework you use it will look different, but this is the gist of it.

The app.signIn() looks as follows.

import passwordless from 'https://unpkg.com/@passwordless-id/connect'

function signIn() {
    // redirects to the authentication/authorization page and back
    passwordless.auth({scope: 'openid avatar email'})
}

// a global `app` object just for the purpose of the demo
window.app = {
    signIn
}
Enter fullscreen mode Exit fullscreen mode

Once the passwordless.auth() method is called, the user will be redirected to the Passwordless.ID UI which will prompt it to create an account or sign in and then to authorize your app to read the openid, avatar and email of your profile. In case the user is already signed in and has granted access, the redirect would come back directly to the app.

When redirecting back, it will append the id_token to the URL hash value (https://example.com/mypage#id_token=...) . The profile can then be extracted from the URL using the following piece of code.

const user = await passwordless.id({scope: 'openid avatar email'})
if(user.signedIn && user.scopeGranted) {
    // great, user.profile contains the information
    // and user.id_token can be sent to APIs
}
    // the user is not signed in or has refused
    // to grant authorization
}
Enter fullscreen mode Exit fullscreen mode

This chuck of code is typically executed when loading the page. The id() function will first look at the current URL and try to parse the id_token in the hash if present. Otherwise, a request will directly be sent to the Passwordless.ID API to obtain it (if the user is signed in and has previously granted the scope of course).

The function always returns. If the user is signed in and has granted the scope, the resulting user would look as follows.

{
 "signedIn": true,
 "scopeGranted": true,
 "id_token": "eyJ0eXAiOiJK...",
 "profile": {
  "nickname": "Johny",
  "picture": "https://ui.passwordless.id/avatars/sam.svg",
  "preferred_username": "johndoe",
  "...": "...more attributes depending on requested scope"
 }
}
Enter fullscreen mode Exit fullscreen mode

That's it! You have signed in a user and read its profile!

What you might also need is a way to sign out the user. Since Passwordless.ID is a centralized authentication system, being signed in or out affects all websites using it. As such, you cannot forcibly sign out users. You can ask them nicely "do you want to sign out?" using the following line.

// redirects the user to the sign out page
passwordless.logout()
Enter fullscreen mode Exit fullscreen mode

This will redirect the user to a sign out page, and once done (or not), back to the current URL.

Everything is now there to sign in, sign out and retrieve the user. Congrats! For the sake of brevity, we will skip the part of displaying the profile and other UI interactions. That is up to you to freely customize according to your website's style.


What about the servers?

The example before shows browser side interaction, with the profile information being directly available. But how could you trust that information server side? That's what the id_token in the response is for! It is a "Json Web Token" (JWT), a signed proof of the user's identity.

The id_token is signed usings Passwordless.ID's secret private key and can be verified using its public key.

There are two ways to validate the token:

  1. Locally, by using one of the many JWT libraries available.

  2. By calling /openid/validate

That way, your servers and APIs can be sure of the user's identity. Each id_token will also possess a sub field according to the OpenID standard, which is a unique user identifier. You may also request the email as scope, but please be aware that an email is optional in Passwordless.ID, so you might not always obtain one even if you request it.


It's only an "Alpha Version"!

Passwordless.ID is not yet fully ready. It's at a proof-of-concept stage. Although it can already be used for testing and integration purposes, it is not yet quite ready for production.

If you think this is a meaningful public service and if you liked the tutorial, please like it and share it! The best way to make it succeed is to spread the word and encourage us. I hope you enjoyed it, I would be glad to hear your feedback!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .