Password-less Web Login with a Mobile App
Apr 17, 2022
Photo by Proxyclick Visitor Management System on Unsplash
If you have used Whatsapp web, you have experienced the use case explored in this post. This post is a proof-of-concept to demonstrate a web login without a password by using a mobile app. The user's flow is as follows:
- A user logs into the mobile app.
- They open the web version in a browser and see a QR code.
- They use the mobile app to scan the QR code, and after a couple of seconds, the browser refreshes to show an authenticated page.
System Context
High-Level Flow
A user logs into the mobile application, which receives an authentication token.
- The user navigates to the web app login page. First, the login page displays the QR code. Next, it waits for a signal to navigate to an authenticated page. The QR code encodes a session identifier also stored in a response cookie.
- The user uses the app to scan the QR code, which sends a POST request with the auth token and decoded session identifier to a login API. The app may also send other helpful information like the username.
- Upon successfully validating the session identifier and user information, the login endpoint associates the user information with the session identifier.
- The web page navigates to an authenticated page after a timeout or on getting a signal, sending back the session cookie it received from step #2. Since the user information is already associated with the session identifier from step #4, the user is identified and shown the authenticated page.
Web App
The web app shows the QR code by requesting the API for one. The snippet shows how:
<img src="http://localhost:3000/qr-code" width="150" height="150" />
After the page is displayed, the web app needs to wait before it can try to access an authenticated page. It can wait in several ways, such as:
- It can start one long timeout, like in the POC. However, production applications should not take this approach.
- Monolithic web applications can check access in short intervals.
- SPAs and Monoliths can wait for a message from a web socket.
setTimeout(() => {
location.href = "http://localhost:3000/dashboard"
}, 30000)
API
The API needs to support two methods in this flow.
GET /qr-code
This endpoint returns an image content type, where the image is the QR code. The QR code encodes a newly generated session token. It also piggybacks a cookie with the same session token.
app.get("/qr-code", async (_req, res) => {
const sessionId = await sessionManager.createSession()
const qrRequestUrl = `${qrCodeBaseUrl}?size=150x150&data=${sessionId}`
https.get(qrRequestUrl, (qrRes) => {
const {statusCode, statusMessage} = qrRes
if (statusCode !== 200) {
console.error(statusMessage)
qrRes.resume()
return
}
res.setHeader("Content-Type", qrRes.headers["content-type"])
res.setHeader("x-qr-session-id", sessionId)
res.cookie("qr-session", sessionId, {
maxAge: 5 * 60 * 1000,
signed: false
})
qrRes.on("data", (chunk) => {
res.write(chunk)
})
qrRes.on("end", () => {
res.end()
})
})
})
POST /login
This endpoint will accept a session token and user identification information such as username, authentication tokens, etc. If the session token and user identification are valid, the endpoint will update the session with the user identification information.
app.post("/login", (req, res) => {
const sidParam = req.query.sessionId
const sessionAttrs = sessionManager.getSession(sidParam)
if (!sessionAttrs) {
res.sendStatus(404)
return
}
const userName = req.query.userName
const updatedAttrs = sessionManager.patchSession(sidParam, {userName})
res.send(updatedAttrs)
})
Mobile App
The user logs into the mobile app and scans the QR code. On the successful scan, it posts the session token from the QR code and user identification data such as username to the /login
API endpoint.
super.onActivityResult(requestCode, resultCode, data)
val result = IntentIntegrator.parseActivityResult(resultCode, data)
if (result != null && this.userName != null) {
val textView = findViewById<TextView>(R.id.textView)
textView.text = result.contents
val call = authService.login(result.contents, this.userName.toString())
call.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
Log.d("MainActivity", response.body() ?: "<empty response>")
}
override fun onFailure(call: Call<String>, t: Throwable) {
Log.e("MainActivity", "Failed to login", t)
}
})
}
Spin it up
Web server
For this POC, we will use Python's built-in HTTP server.
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
API
$ npm start
> qr-api@0.0.1 start
> node index.js
Listening on port 3000
Open the browser
Open the Mobile
For the POC, there is no actual authentication in the mobile app. Instead, it just accepts a username to log in. Then, on successfully scanning the QR code, it will post the username and decoded session token to the /login
API endpoint.
API Requests
The API logs the requests it gets. In this flow, it gets the following requests in order. The log also shows the response code for each request.
curl http://localhost:3000/qr-code -o qr.png
#GET /qr-code 200
curl -v http://localhost:3000/login\?sessionId\=0.4851294361\&userName\=sayantam -X POST
#POST /login?sessionId=0.4851294361&userName=sayantam 200
curl -v http://localhost:3000/dashboard --cookie "qr-session=0.4851294361"
#GET /dashboard 200
The /dashboard
request is the authenticated page.
Source Code
The source code for the POC is divided into two parts in a mono-repository.
qr-reader
: Mobile Appqr-api
: Web App & API
Related Posts
Effective Dependency Inversion
Dec 25 2022
Can you spot the issues in the following code snippet.
Flux-CD Pattern for AWS CDK8s Services
Jul 9 2023
AWS Cloud Development Kit for Kubernetes called generates Kubernetes manifest files for Kubernetes () deployments and services.
How Headless CMS work
Jun 25 2023
Headless CMSs came about because it is hard to build a single platform that content writers like using and software developers like…
How Time-based OTP (TOTP) works
Sep 10 2023
The adoption of two-factor authentication (2FA) has been steadily growing over the past three years.
JAM Stack
Nov 29 2020
In a previous post on how this blog works, I referred to Gatsby and GraphQL.
The First Post
Apr 20 2019
Just like fashion, technology tends to go in circles, and static web sites are rising in popularity for content.