As the digital world continues to expand, ensuring the security of web applications and protecting user data has become more crucial than ever. One key aspect of web development security is authentication, which involves verifying the identity of users accessing web applications.
With a plethora of authentication strategies available, such as token and session authentication, OAuth, OIDC, and standards like SAML and JWTs, and more, it can be overwhelming for developers to navigate the landscape of options.
In this comprehensive guide, we will delve into various authentication strategies, discussing their strengths and weaknesses, best practices, and practical implementation tips. Whether you're a seasoned developer or just starting out, this guide aims to provide you with a comprehensive overview of authentication strategies to help you secure the web and protect user data effectively.
What is Authentication?
Authentication is the process of verifying the identity of users accessing web applications. This is done by asking users to provide credentials, such as a username and password, to prove that they are who they say they are. When users provide their credentials, they are typically authenticated by a server, which verifies the credentials and either grants or denies access to the application.
Authentication vs. Authorization
While Authentication makes sure that the users are who they say they are, Authorization determines what they are allowed to do once they are authenticated.
For example, a user may be authenticated as an administrator, but may not be authorized to access certain parts of the application.
Another example is, a user can only have permissions to access and modify their own data, but not other users' data.
When a user logs in, the server creates a session and stores it in a database. This means that the connection is stateful. The server then sends a session ID to the client in the form of a cookie.
When the user makes a request to the server, that session ID is included in the request, then the server verifies the session ID in the cookie and grants access to the application if the session is valid. If not, the user/client is denied from access and probably redirected to the main/login page.
When the user logs out, the session is destroyed and the cookie is deleted.
- Easy to implement
- Small size
- Long lifespan
- Not scalable, as the server needs to store session information in a database. This means that the server needs to handle a lot of traffic and requests, which can be expensive and time consuming.
- Not secure, as the session ID is stored in a cookie, which is prone to XSS and CSRF attacks.
Token-based authentication is very similar to session-based authentication, but instead of storing session information in a database, the server generates a unique JWT token that is sent to the client that and stored in a cookie. This means that the connection is stateless and we don't have the overhead of storing session information in a database.
A JWT token is a string that contains a JSON object that is encoded using Base64. It is signed using a secret key, which is used to verify the token.
When the user makes a request to the server, the token is included in the request in the Authorization header prefixed with the word "Bearer ", then the server verifies the token and grants access to the application if the token is valid. If not, the user/client is denied from access and probably redirected to the main/login page.
When the user logs out, the cookie is destroyed.
- Scalable, as it's not saved in a database.
- Secure, as the token is signed using a secret key, which is used to verify the token.
- You can save info about the user in the token, such as the user's role, permissions, etc.
- Not easy to implement natively, as you need to implement a lot of logic to verify the token and handle errors. but you can use an external library to make it easier.
- Shorter lifespan.
- Data overhead, as it's generally bigger than a session id and the more data you save in the token, the bigger the token will be.
- Is still vulnerable to XSS and CSRF attacks, as the token is stored in a cookie.
What should you do to protect your cookies from XSS and CSRF attacks?
- Use the Secure flag, which ensures that the cookie is only sent over HTTPS. This means that the cookie can only be accessed by the server over HTTPS.
- Set the SameSite flag to Strict, which ensures that the cookie is only sent with requests originating from the same site.
- if you are willing to go the extra mile, you can use a refresh token and an access token. which is the way I prefer to do it.
OAuth stands for "Open Authorization" which is a standard for authorization, commonly used as a way for users to grant websites or applications access to their information on other websites but without giving them the passwords. This mechanism is provided by companies such as Facebook, Twitter, and Google to permit the users to share information about their accounts with third party applications or websites.
OAuth 1.0 vs. OAuth 2.0
OAuth 1.0 and OAuth 2.0 are two different versions of the OAuth protocol. While both versions serve the same fundamental purpose, they have some differences in their implementation and features.
Here's a brief comparison between OAuth 1.0 and OAuth 2.0:
Signature-based: OAuth 1.0 uses a signature-based authentication method, where each request from the client includes a signature based on a shared secret and a timestamp. This makes it secure and reliable for authentication.
Token and Secret: OAuth 1.0 uses both a token and a secret to authenticate the client and access protected resources.
Complex Flow: OAuth 1.0 uses a three-legged authentication flow, which involves obtaining a request token, user authorization, and then exchanging the request token for an access token.
Simpler Flow: OAuth 2.0 uses a simpler two-legged authentication flow, which involves obtaining an access token directly from the authorization server.
Token-based: OAuth 2.0 uses a token-based authentication method, where the client presents an access token in each request to access protected resources.
Multiple Grant Types: OAuth 2.0 supports multiple grant types, including authorization code, implicit, password, and client credentials, providing more flexibility in authentication scenarios.
Scopes: OAuth 2.0 uses scopes to specify the permissions requested by the client, which can be coarse-grained or fine-grained, depending on the implementation.
Widely Adopted: OAuth 2.0 has gained widespread adoption and is widely used by popular web services and APIs for authentication and authorization.
In summary, OAuth 1.0 is a signature-based, complex flow with granular permissions, while OAuth 2.0 is a token-based, simpler flow with support for multiple grant types and scopes. OAuth 2.0 is more widely adopted, but the choice between OAuth 1.0 and OAuth 2.0 depends on the specific requirements of the application and the level of security and complexity desired.
OAuth 2.0 flow
The most commonly used OAuth 2.0 flow is the Authorization Code Flow. Here's a step-by-step overview of the Authorization Code Flow:
User initiates the authorization process: The user clicks a "Login with (Client Application)" button or takes some other action to initiate the authorization process in the client application.
Client redirects user to authorization server: The client application redirects the user to the authorization server with a request that includes the client ID, redirect URI, and desired scope of access.
User authenticates with the authorization server: The user is prompted to authenticate with the authorization server, providing their credentials (e.g., username and password) to prove their identity.
User grants authorization: After successful authentication, the user is presented with a consent screen where they can review and grant or deny the client's request for access to their protected resources.
Authorization server redirects user back to client: If the user grants authorization, the authorization server redirects the user back to the client application's redirect URI with an authorization code included in the URL.
Client exchanges authorization code for access token: The client application sends a request to the authorization server to exchange the authorization code for an access token. This request includes the authorization code, client ID, client secret, and redirect URI.
Authorization server validates and issues access token: The authorization server validates the authorization code, client ID, client secret, and redirect URI. If they are valid, the authorization server issues an access token and optionally a refresh token to the client.
Client uses access token to access protected resources: The client can then use the received access token to make authorized requests to the protected resources on behalf of the user. The access token serves as proof of authorization.
The Authorization Code Flow is widely used in web applications. However, it also adds additional complexity due to the multiple steps involved, making it more suitable for server-side applications.
OpenID Connect (OIDC)
OpenID Connect is an authentication layer on top of the OAuth 2.0 protocol.
It adds an additional JWT token called an ID token to the Authorization Code Flow. The ID token contains information about the user, such as their name and email address. This information can be used to personalize the user experience and establish an identity for the user.
Security Assertion Markup Language (SAML) is an XML-based standard for exchanging authentication and authorization data between security domains. It is used by companies to allow their employees to access applications and services from multiple vendors using a single set of credentials. Providing what is known as a single sign-on (SSO) experience for users.
Web Authentication (WebAuthn) is a new W3C standard for passwordless authentication on the web. It allows users to authenticate to websites using biometric authentication, such as fingerprint or facial recognition, or security keys, such as Yubikeys.
It works by using a public key cryptography system to generate a unique cryptographic key pair for each user. The private key is stored on the user's device, while the public key is encrypted and stored on the server.
When the user attempts to log in, the server sends a challenge to the user's device, which the user then signs with their private key. The server then verifies the signature using the public key, and if it's valid, the user is authenticated.
Which one should you use?
For complex applications with multiple users, I would recommend using OIDC. It's more secure and widely adopted.
For web applications only, I would recommend using Token-based authentication with the precautions stated above.
SAML is your best bet if you are building an enterprise application, but sometimes it can be a bit much to handle.
WebAuthn is another great option for web applications, but it's still in its early stages and not widely adopted yet. and it introduces one new challenge - what if the user loses the authenticator.
In my opinion, you can't go wrong with OAuth/OIDC. It's a bit more complex than some other options, but it's secure and widely adopted.
There are other ways to authenticate users, such as 2 factor authentication, One Time passwords, etc. but these are usually used in conjunction with one of the methods mentioned above.
I hope this was helpful. If you have any questions, feel free to contact me.