Challenges of implementing passwordless authentication

Igor Minin
Core Security
Published in
9 min readJan 12, 2021

Here at Cores, we’ve been trying to create a great solution for passwordless authentication. Essentially everyone uses phones these days for anything from internet browsing to mobile payments. Why not use it for web authentication too?

In this article I will go through some of the challenges we encountered on the long way of implementing this solution.

Our approach

We wanted to implement Public Key Infrastructure (PKI) based web authentication. The idea behind PKI is just basic asymmetric cryptography — it allows for much more secure passwordless login possibilities rather than passwords. Passwords are also not very convenient and have a lot of potential security issues. There’s WebAuthN standard which is practically a PKI solution for web authentication but it is not quite there yet for the usage scenarios we wanted to achieve. You can see more about password problems and WebAuthN in our article “Trade offs behind corporate account security”.

The first thing we’ve been focused on was to only store private keys on user devices and allow authentication (and later authorization too) using public keys that are available for anyone in the system. Our goal was not to build just a centralized service but a p2p one. This would allow us to fully leverage the PKI and allow for the best security by design.

For actual authentication it’s a common practice to use existing standards like OAuth and OIDC. OIDC is decentralized by design and can be used in a peer-to-peer manner too. We started our transport abstraction to expand on that but in the end it didn’t really play out — more on that later. So we just left the abstraction for later use in possible decentralization.

And in the end our architecture looks like this:

To sum it up: we have a mobile app and a server both of which are based on the same mobile library. Transport abstraction is used as a relay between server and mobile. We provide JS library and web admin UI (also based on the same JS lib) for our clients. Clients can call OAuth and OIDC APIs on our server directly or by using our provided JS library.

The use case is: user opens up a secured site, scans QR code with his mobile phone and is logged in.

UX first

The first thing you’ll have to consider doing passwordless auth is to do it with the best UX for the user in mind. Authentication is done quite regularly and this kind of action should be very fast and easy-to-use. App should be simple but understandable.

It should take a minimal amount of time for users to be able to log in. There should be a fast and easy way to log in the second time. The application should load fast etc.

As a result — any design decisions or technical implementation choices should be first validated with UX in mind. If those will make your app slow, unresponsive or require too many additional steps — they do not fit. Let’s move on taking this into account.

Native app or JS-based

Nowadays it’s a very common strategy to implement mobile apps with JavaScript-based technologies like React native. There are several alternatives but in general, they provide a nice way to only do “one code for every platform” (most of the time it’s Android and iOS).

While this is a very good idea for many reasons (fast development time, high code reuse, easier to manage single language codebase, etc) it still has some downsides when compared to the native app development. There are numerous comparisons on the web and a dozen of good articles to consider one of the approaches but I recommend checking out this series of articles by Airbnb - it may be somewhat outdated today, but still valid as a general picture.

Anyway, we’ve had our own experience in the development of JS-based mobile apps and had our share of issues with them too. After a while we’ve decided to switch to the native apps and have been doing it for a few years now — no more issues.

Code reuse

As great as the native app development can be compared to some issues we could have with the JS framework, it still means you’ll have to maintain new codebases in new languages. In our case — we’re doing Swift for iOS and Kotlin for Android. Writing everything in two languages can be super time consuming and tedious and does not seem like effective work.

To solve this we decided to use gomobile to build a native library that can be used in both Android and iOS.

Our whole backend code is already in Go and it has everything related to the transport and storage logic. A lot of business logic is shared with the backend as well so it makes a lot of sense to reuse it. Using one library for both mobile platforms allows us to focus on the presentation layer for each of them and not to rewrite the same business logic a few times.

Having a single library between mobile and backend has its upsides too — you can test everything with Go tests. Yes, it is not a full e2e test (which we also do for each platform) but it can still validate 90% of application logic since all of the calls are done with the library.

Storage options

One of the biggest questions you’ll come up with your solution would be to decide on storage to use in your app.

As we’ve been trying to do a p2p-focused solution we’ve tried some distributed storage options first. These options were great from a technical perspective but usually are much slower than centralized counterparts and since passwordless auth should be as fast as possible — they do not quite fit.

The obvious choice here would be to go with any kind of centralized database and there are literally hundreds of different DBs out there each with its own strengths for specific tasks. If you’ll decide on a similar approach as we did with doing Go-based library e.g. gomobile I would highly recommend using goleveldb as a go native database, meaning you don’t need anything else to run it.

It’s also super fast and easy to use. Of course it’s a LevelDB so it is just a key/value storage meaning if you need some complex queries or storage operations you might need something else (note there are also a few more powerful go native DBs like Badger and Bolt though still key/value).

Possible architecture

Before starting the implementation we first needed to decide on architecture so we’ve been investigating a few different possibilities.

The very first option would seem to be just a plugin for a browser, so that your phone would directly interact with the browser, e.g.:

Unfortunately, this is not quite possible today. At least not in a sense that would make it easy-to-use or be “seamless”. Browser add-ons usually have very limited functionality so most of the networking you could do is doing some HTTP calls or using a WebSocket.

WebSocket

WebSocket would seem like a great solution here being fully duplex however in case of a mobile phone being used as a means of authentication — someone should serve as a WebSocket server. Most of the time mobile networks are very dynamic, sometimes assigning different IPs to the same devices even for a short session. This means you cannot rely on the device IP being the same each time.

The same goes for the browser — you can’t just make your browser become a server open to the internet to accept WebSocket connections. It is possible with static IPs and private domains, but these cases are far from achievable for most people.

Overall real Mobile ↔ Browser p2p communication is not quite feasible just yet, we’ll have to find another solution.

Relay

So some kind of “relay” or server is needed to act as a middleman between the browser and the phone anyway.

But in this case plugin in the browser does not seem to be a good solution for a user UX — since you now have a server anyway there’s no point in forcing the user to install anything. Ideally, you can just remove the plugin and rely on basic JS and existing standards to do authentication — OAuth or OIDC.

Transports and p2p networking with Go

I already mentioned that we’ve been focusing on making peer-to-peer connectivity in our platform. We’ve done a lot of experiments and tested our internal p2p networking stack based on Kademlia which was kinda ok for Cloud apps but impossible to use in mobile clients. The same stands for mature solutions like libp2p etc…

It would be too much for this post and could be a separate article — would you like to read it? Let me know in the comments.

OAuth and OIDC server

We needed to create a server that will support both OAuth and OIDC for future clients that will use our platform.

Keycloak

The very first option to do the server was to just implement the OAuth provider and use Keycloak as a server for all possible logic. We’ve had a very big and good experience working with Keycloak — but relying on it means that our solution is highly dependent on different software. Sure Keycloak can do practically anything that you can ever need, but we wanted to create a standalone service that could function as a provider for Keycloak but only if our customer needs it. Otherwise, our server should be fully independent and be able to serve both OAuth and OIDC requests.

Go and OIDC

As stated already — all of our backend code (and a lot of mobile code too) is written in Go so the first question was to decide on how we would implement the OIDC server in Golang.

While OAuth is not that hard to implement from scratch (though there is a lot of work needed anyway) OIDC specification is a different beast.

OIDC is quite big and there are a lot of possible flows it could be used with so it would be very hard to implement by yourself.

Full-fledged servers

There are some Go implementations that you might use though. First of all, there are two existing servers that provide most of the OAuth and OIDC functionality: ORY Hydra and Dex.

Both of them have everything you might need for your needs and can be used as a base for your own authentication server. They both also have Apache 2.0 licenses so it’s entirely possible to use for your implementation (with the copyright notice of course).

However, if you’re just interested in OAuth/OIDC core functionality like us, chances are you don’t want to use/fork hydra or dex. It would be best to just use some kind of library or framework for that.

Fosite to rescue

And the ORY team has the exact solution for this — they created Fosite framework to allow you to implement this kind of logic easily. They also built their own Hydra project with it too. During our work with fosite we’ve found some issues like .well-known endpoint not supported yet which only exists in Hydra. But the overall Fosite allows you to do pretty much everything you’ll need for your OIDC server and we’re very happy with it.

In the end, Fosite allowed us to write our own OIDC server which can now be officially certified by OpenID. The certification test suite validates most of the calls and flows that can be used with OIDC so we can be sure that customers will be ok using it.

Parting thoughts

These are only some of the challenges we encountered during the development of Cores. Let us know your opinions or any questions on any other possible challenges during development. Feel free to leave any feedback too.

--

--

Igor Minin
Core Security

I’m a passionate developer who loves new tech. I’m currently mostly use Go and I’m also a big fan of both VueJS and Typescript