Web Authentication Methods Compared | TestDriven.io

In this article, we'll look at the most commonly used methods for handling web authentication from the perspective of a Python web developer.

While the code samples and resources are meant for Python developers, the actual descriptions of each authentication method are applicable to all web developers.

Contents

Authentication vs Authorization

Authentication is the process of verifying the credentials of a user or device attempting to access a restricted system. Authorization, meanwhile, is the process of verifying whether the user or device is allowed to perform certain tasks on the given system.

Put simply:

  1. Authentication: Who are you?
  2. Authorization: What can you do?

Authentication comes before authorization. That is, a user must be valid before they are granted access to resources based on their authorization level. The most common way of authenticating a user is via username and password. Once authenticated, different roles such as admin, moderator, etc. are assigned to them which grants them special privileges to the system.

With that, let's look at the different methods used to authenticate a user.

HTTP Basic Authentication

Basic authentication, which is built into the HTTP protocol, is the most basic form of authentication. With it, login credentials are sent in the request headers with each request:

"Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=" your-website.com

Usernames and passwords are not encrypted. Instead, the username and password are concatenated together using a : symbol to form a single string: username:password. This string is then encoded using base64.

>>> import base64
>>>
>>> auth = "username:password"
>>> auth_bytes = auth.encode('ascii') # convert to bytes
>>> auth_bytes
b'username:password'
>>>
>>> encoded = base64.b64encode(auth_bytes) # base64 encode
>>> encoded
b'dXNlcm5hbWU6cGFzc3dvcmQ='
>>> base64.b64decode(encoded) # base64 decode
b'username:password'

This method is stateless, so the client must supply the credentials with each and every request. It's suitable for API calls along with simple auth workflows that do not require persistent sessions.

Flow

  1. Unauthenticated client requests a restricted resource
  2. HTTP 401 Unauthorized is returned with a header WWW-Authenticate that has a value of Basic.
  3. The WWW-Authenticate: Basic header causes the browser to display the username and password promot
  4. After entering your credentials, they are sent in the header with each request: Authorization: Basic dcdvcmQ=

http basic auth workflow

Pros

Cons

Packages

Code

Basic HTTP Authentication can be easily done in Flask using the Flask-HTTP package.

from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
auth = HTTPBasicAuth()

users = {
    "username": generate_password_hash("password"),
}


@auth.verify_password
def verify_password(username, password):
    if username in users and check_password_hash(users.get("username"), password):
        return username


@app.route("/")
@auth.login_required
def index():
    return f"You have successfully logged in, {auth.current_user()}"


if __name__ == "__main__":
    app.run()

Resources

HTTP Digest Authentication

HTTP Digest Authentication (or Digest Access Authentication) is a more secure form of HTTP Basic Auth. The main difference is that the password is sent in MD5 hashed form rather than in plain text, so it's more secure than Basic Auth.

Flow

  1. Unauthenticated client requests a restricted resource
  2. Server generates a random value called a nonce and sends back an HTTP 401 Unauthorized status with a WWW-Authenticate header that has a value of Digest along with the nonce: WWW-Authenticate: Digest nonce="44f0437004157342f50f935906ad46fc"
  3. The WWW-Authenticate: Basic header causes the browser to display the username and password prompt
  4. After entering your credentials, the password is hashed and then sent in the header along with the nonce with each request: Authorization: Digest username="username", nonce="16e30069e45a7f47b4e2606aeeb7ab62", response="89549b93e13d438cd0946c6d93321c52"
  5. With the username, the server obtains the password, hashes it along with the nonce, and then verifies that the hashes are the same

http basic auth workflow

Pros

Cons

Packages

Code

The Flask-HTTP package supports Digest HTTP Authentication as well.

from flask import Flask
from flask_httpauth import HTTPDigestAuth

app = Flask(__name__)
app.config["SECRET_KEY"] = "change me"
auth = HTTPDigestAuth()

users = {
    "username": "password"
}


@auth.get_password
def get_user(username):
    if username in users:
        return users.get(username)


@app.route("/")
@auth.login_required
def index():
    return f"You have successfully logged in, {auth.current_user()}"


if __name__ == "__main__":
    app.run()

Resources

Session-based Auth

With session-based auth (or session cookie auth or cookie-based auth), the user's state is stored on the server. It does not require the user to provide a username or a password with each request. Instead, after logging in, the server validates the credentials. If valid, it generates a session, stores it in a session store, and then sends the session ID back to the browser. The browser stores the session ID as a cookie, which gets sent anytime a request is made to the server.

Session-based auth is stateful. Each time a client requests the server, the server must locate the session in memory in order to tie the session ID back to the associated user.

Flow

http session auth workflow

Pros

Cons

Packages

Code

Flask-Login is perfect for session-based authentication. The package takes care of logging in, logging out, and can remember the user for a period of time.

from flask import Flask, request
from flask_login import (
    LoginManager,
    UserMixin,
    current_user,
    login_required,
    login_user,
)
from werkzeug.security import generate_password_hash, check_password_hash


app = Flask(__name__)
app.config.update(
    SECRET_KEY="change_this_key",
)

login_manager = LoginManager()
login_manager.init_app(app)


users = {
    "username": generate_password_hash("password"),
}


class User(UserMixin):
    ...


@login_manager.user_loader
def user_loader(username: str):
    if username in users:
        user_model = User()
        user_model.id = username
        return user_model
    return None


@app.route("/login", methods=["POST"])
def login_page():
    data = request.get_json()
    username = data.get("username")
    password = data.get("password")

    if username in users:
        if check_password_hash(users.get(username), password):
            user_model = User()
            user_model.id = username
            login_user(user_model)
        else:
            return "Wrong credentials"
    return "logged in"


@app.route("/")
@login_required
def protected():
    return f"Current user: {current_user.id}"


if __name__ == "__main__":
    app.run()

Resources

Token-Based Authentication

This method uses tokens to authenticate users instead of cookies. The user authenticates using valid credentials and the server returns a signed token. This token can be used for subsequent requests.

The most commonly used token is a JSON Web Token (JWT). A JWT consists of three parts:

All three are base64 encoded and concatenated using a . and hashed. Since they are encoded, anyone can decode and read the message. But only authentic users can produce valid signed tokens. The token is authenticated using the Signature, which is signed with a private key.

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. - IETF

Tokens don't need not be saved on the server-side. They can just be validated using their signature. In recent times, token adoption has increased due to the rise of RESTful APIs and Single Page Applications (SPAs).

Flow

token auth workflow

Pros

Cons

Packages

Code

The Flask-JWT-Extended package offers a lot of possibilities for handling JWTs.

from flask import Flask, request, jsonify
from flask_jwt_extended import (
    JWTManager,
    jwt_required,
    create_access_token,
    get_jwt_identity,
)
from werkzeug.security import check_password_hash, generate_password_hash

app = Flask(__name__)
app.config.update(
    JWT_SECRET_KEY="please_change_this",
)

jwt = JWTManager(app)

users = {
    "username": generate_password_hash("password"),
}


@app.route("/login", methods=["POST"])
def login_page():
    username = request.json.get("username")
    password = request.json.get("password")

    if username in users:
        if check_password_hash(users.get(username), password):
            access_token = create_access_token(identity=username)
            return jsonify(access_token=access_token), 200

    return "Wrong credentials", 400


@app.route("/")
@jwt_required
def protected():
    return jsonify(logged_in_as=get_jwt_identity()), 200


if __name__ == "__main__":
    app.run()

Resources

One Time Passwords

One time passwords (OTPs) are commonly used as confirmation for authentication. OTPs are randomly generated codes that can be used to verify if the user is who they claim to be. Its often used after user credentials are verified for apps that leverage two-factor authentication.

To use OTP, a trusted system must be present. This trusted system could be a verified email or mobile number.

Modern OTPs are stateless. They can be verified using multiple methods. While there are a few different types of OTPs, Time-based OTPs (TOTPs) is arguably the most common type. Once generated, they expire after a period of time.

Since you get an added layer of security, OTPs are recommended for apps that involve highly sensitive data, like online banking and other financial services.

Flow

The traditional way of implementing OTPs:

How TOTPs work:

How OTP agents like Google Authenticator, Microsoft Authenticator, and FreeOTP work:

Pros

Cons

Packages

Code

The PyOTP package offers both time-based and counter-based OTPs.

from time import sleep

import pyotp

if __name__ == "__main__":
    otp = pyotp.TOTP(pyotp.random_base32())
    code = otp.now()
    print(f"OTP generated: {code}")
    print(f"Verify OTP: {otp.verify(code)}")
    sleep(30)
    print(f"Verify after 30s: {otp.verify(code)}")

Example:

OTP generated: 474771
Verify OTP: True
Verify after 30s: False

Resources

OAuth and OpenID

OAuth/OAuth2 and OpenID are popular forms of authorization and authentication, respectively. They are used to implement social login, which is a form of single sign-on (SSO) using existing information from a social networking service such as Facebook, Twitter, or Google, to sign in to a third-party website instead of creating a new login account specifically for that website.

This type of authentication and authorization can be used when you need to have highly-secure authentication. Some of these providers have more than enough resources to invest in the authentication itself. Leveraging such battle-tested authentication systems can ultimately make your application more secure.

This method is often coupled with session-based auth.

Flow

You visit a website that requires you to log in. You navigate to the login page and see a button called "Sign in with Google". You click the button and it takes you to the Google login page. Once authenticated, you're then redirected back to the website that logs you in automatically. This is an example of using OpenID for authentication. It lets you authenticate using an existing account (via an OpenID provider) without the need to create a new account.

The most famous OpenID providers are Google, Facebook, Twitter, and GitHub.

After logging in, you navigate to the download service within the website that lets you download large files directly to Google Drive. How does the website get access to your Google Drive? This is where OAuth comes into play. You can grant permissions to access resources on another website. In this case, write access to Google Drive.

Pros

Cons

Packages

Looking to implement social login?

Looking to run your own OAuth or OpenID service?

Code

You can implement GitHub social auth with Flask-Dance.

from flask import Flask, url_for, redirect
from flask_dance.contrib.github import make_github_blueprint, github

app = Flask(__name__)
app.secret_key = "change me"
app.config["GITHUB_OAUTH_CLIENT_ID"] = "1aaf1bf583d5e425dc8b"
app.config["GITHUB_OAUTH_CLIENT_SECRET"] = "dee0c5bc7e0acfb71791b21ca459c008be992d7c"

github_blueprint = make_github_blueprint()
app.register_blueprint(github_blueprint, url_prefix="/login")


@app.route("/")
def index():
    if not github.authorized:
        return redirect(url_for("github.login"))
    resp = github.get("/user")
    assert resp.ok
    return f"You have successfully logged in, {resp.json()['login']}"


if __name__ == "__main__":
    app.run()

Resources

Conclusion

In this article, we looked at a number of different web authentication methods, all of which have their own pros and cons.

When should you use each? It depends. Basic rules of thumb:

  1. For web applications that leverage server-side templating, session-based auth via username and password is often the most appropriate. You can add OAuth and OpenID as well.
  2. For RESTful APIs, token-based authentication is the recommended approach since it's stateless.
  3. If you have to deal with highly sensitive data, you may want to add OTPs to your auth flow.

Finally, keep in mind that the examples shown just touch the surface. Further configuration is required for production use.

::...
免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


订阅 substack 体验古早写作:


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::