Authentication in web apps

Posted on 2021-01-09 byVlad Călin

This post is going to be a slightly more technical one where I will talk about what authentication is, how it looks like under the hood and what you should consider if you decide (or are tasked) to implement it from scratch.

I decided to tackle this topic after I worked for a while on integrating django-allauth for my current side-project/startup Amethyst Platform.

What is authentication?

First, let's see what is authentication. A common mis-conception I see around the industry is that people put authentication and authorization in the same bag. But actually, they are two totally different concepts that need to be treated differently.

First, authentication is the process through which you decide on the identity of somebody or something that interacts with your systems. A real person? A script? Are they known? Do they have a real identity? Are they anonymous?

The authentication process is usually split in two: the phase in which the user (if they are a real person) needs to prove their identity (either by providing an email together with a shared secret such as a password, or a code sent on email) and the phase where they receive a temporary proof that their identity is valid and use it in subsequent actions to attach their identity to that action.

More specific, when a user logs in on a website, they usually receive a token that asserts that they successfully logged in and proved to the system that they are indeed who they are. Then, they use that token and attach it somehow to every subsequent request they do (eg. when they click on a link, the token will be sent with the request to fetch the new page) so that the system knows what users did that action. This way, the user is allowed to do certain actions that other users that did not log in can not, and maybe even receive personalized content specially crafted for them.

Proof of identity

This can be done in multiple ways, but they can be boiled down to three main categories:

  • proof based on knowing a shared secret. Examples of this kind of authentication:
    • password based authentication: when the user needs to provide a password when they log in, and the server checks against its internal records to make sure it matches (note: the server MUST NOT store the password as it is, they need to hash it with a salt, using industry standard algorithms such as BCrypt)
    • token based authentication: the user proves that they know a specific secret that is hard to guess (usually based on random generation) that is included with each action. This is usually more used in machine-to-machine communication, because it doesn't require any input from either side (except the secret generation part). Example of such proof of identity is AWS's secret key usage in scripts and JWT.
    • cryptographic proof: using public-private cryptographic keys and encryption, a user can offer proof that they are indeed who they say they are. The public key is used by the server to create challenges (usually encrypting a message) and the user must correctly decrypt it. They can do that only by knowing the private key, in which case that is proof that they have that information and it is correct (private keys are supposed to be secret and known only by an individual, in no case they must be shared). Examples of systems/mechanisms that use this kind of authentication are: SSL/HTTPS, SSH.
  • proof based on trusted entities. This is also known as 3rd party authentication, and it revolves around using OAUTH/OAUTH2 authentication flows using a well-known platform to confirm the indentity of some user. The steps for such flow are:
    1. for example, authenticating with Facebook: the user is redirected to a special page on facebook.com, where they are asked to share some information with our system
    2. depending on whether the user accepts or not to do that, Facebook redirects to our system, offering some extra information (a code with which the system can then use to access the info the user agreed to share). Then the system knows that Facebook basically said "this is the X user on our platform, and you are allowed to access some of their information".
    3. From now on, the system knows that the associated user X from Facebook is a real person and they are allowed to access our system as well.
  • proof based on having access to other physical devices or other external services.
    • password-less authentication - the system sends a uniquely generated code (that is usually embedded in a link) and sent to the user usually by email. By accessing that link, the user basically proves that they have access to that email inbox, thus it proves that is the owner of the specified email address.
    • one time passwords - somehow a one time password (usually a short code) is generated and is communicated to the user on some physical device that must be in their possession. If they correctly provide that password they basically prove that they have access to that physical device.
      • the code can generated online - in this case, the code is generated by the system and sent through certain live communication channels, such as a phone message.
      • the code can be generated offline as well - in this case, the code is generated based on a combination of shared information between the server and the device that can generate the code, and the current time. This way, the device in user's possession gives a new code periodically (usually in 30 or 60 seconds intervals), and the system can compute the code itself and if they match, the user just proved that they have access to the physical device. This is usually used by banks (with special token devices), or other web apps in combination with some mobile apps such as Google Authenticator.

There are more authentication methods out there (eg. biometric data), but the ones from above cover the most common 90% of them. If you will implement an authentication system from scratch, you will most certainly be required to implement one of these methods, or a combination of multiple ones for two factor apps.

Attaching the identity to subsequent actions

After a user successfully proves their identity, they usually get back some uniquely crafted information through which the system can attach the identity of the user to some action that is performed.

I will talk mainly about how this can be done with communcation over the HTTP protocol, in the context of web applications. To better understand the next parts, you should also have a decent knowledge and understanding of how HTTP works (eg. methods/verbs, headers, body).

Sessions

Session based authentication is based on cookies (pieces of information that are automatically managed by the browser using request headers such as Cookie and response headers such as Set-Cookie).

First time a user accesses a web application, they receive a session id which is unique and is stored in a cookie. On the system, there is certain data that is attached to that session id, but it is only visible for the system, it is not publicly accessible.

Once the user proves their identity, on the system, some user identifiable information (such as the internal user ID) is attached to the session id. For subsequent actions, the browser will send the same session id cookie, and the system will know that user "claimed" that session, and attach this user's information to it.

JWT

Javascript Web Token is a method that is similar to using cookies and works on the same principle with the differences:

  • it is usually not stored in the cookies, but it is sent via the Authorization header.
  • the identifiable information of the user is stored inside the token, and not in the private session information.
  • the data is cryptographically protected against tampering with cryptographic signatures.

The flow works like this:

  • the user proves its identity, and then the system builds a JWT token that contains some arbitrary data, algorithm information and a signature.
  • the user/client stores somewhere that token, and then sends it in every subsequent request, in the Authorization header.

The main advantage is that the system can then figure out what user did the request without any database/storage access (where the session data is stored) and can easily verify the authenticity of the token using the signature.

The signature requires a secret signing key which is only known by the server.

The main disadvantage is that JWT tokens can not be revoked, and they are available until their expiration time. This means that you will need to create extra protections to protect against token leaking. If an attacker manages to steal a token from other user, they can use it to impersonate the user until the token expires.

Plain Token

From the user's point of view, this type of authentication is the same as using JWT, with the distinction that the key is not cryptographically secured. The generated token must be saved somewhere using a dedicated storage such as a database or a cache, and must be retrieved every time.

But unlike JWT, they can be revoked. If a token can not be found in storage, then it is invalid, and the access is denied for the request.

Other

There are other types of authentication methods as well, such as Basic Authentication where the username and password are sent in the Authorization header encoded in base64 but it is insecure in untrusted environment and if it must be used, it has to be over HTTPS. Base64 encoding offers absolutely no protection and you can consider that the credentials are basically sent in plain text.

Conclusion

I hope this post was interesting to read, and thank you for bearing until the end. I hope this information will aid you in choosing an authentication flow suitable for your app, or if you are not building anything at the moment, aid you in learning new things.

If you enjoyed it and learned something new, or think you have people in your network that would like to read this, don't hesitate to share! It would mean a lot!