← Back to DevBytes

Authentication and Authorization in Celery Applications

Authentication and Authorization in Celery Applications

Authentication and authorization are critical security concerns in any distributed system, and Celery applications are no exception. Celery is a powerful asynchronous task queue/job queue based on distributed message passing. While it excels at offloading work from web servers, its flexibility also introduces attack surfaces if not secured properly. This tutorial explains what authentication and authorization mean in the context of Celery, why they matter, how to implement them with practical code examples, and the best practices to follow.

What is Authentication and Authorization in Celery?

In a typical Celery setup, you have:

Authentication in this context verifies the identity of a component trying to produce or consume tasks. For example, a producer must prove it is allowed to send tasks to a specific broker. A worker must prove it is allowed to consume tasks from that broker.

Authorization determines what an authenticated component is permitted to do. For instance, a producer may be allowed to send tasks of certain types only, or a worker may be restricted from accessing certain result backends.

While Celery itself does not enforce application-level authentication/authorization by default (it delegates broker security to the message transport layer), you can implement additional layers to control access at the task level.

Why It Matters

How to Implement Authentication and Authorization in Celery

There are multiple levels at which you can enforce security. We’ll cover broker-level authentication, task-level authentication using custom base tasks, message signing, and integration with web frameworks like Django or Flask.

1. Broker-Level Authentication

The first line of defense is securing the message broker itself. Both RabbitMQ and Redis support authentication (username/password, TLS certificates).

RabbitMQ example:

# In your Celery configuration (celeryconfig.py or Django settings)
broker_url = 'amqp://myuser:mypassword@localhost:5672/my_vhost'

For production, never hardcode credentials. Use environment variables or a secret manager.

Redis example:

broker_url = 'redis://:password@localhost:6379/0'

Additionally, use TLS encryption to protect credentials in transit:

broker_use_ssl = {
    'ssl_cert_reqs': ssl.CERT_REQUIRED,
    'ssl_ca_certs': '/path/to/ca.crt',
    'ssl_certfile': '/path/to/client.crt',
    'ssl_keyfile': '/path/to/client.key',
}
broker_url = 'amqps://user:pass@host:5671/vhost'

2. Task-Level Authentication Using Custom Base Tasks

Even if the broker is secured, any authenticated producer can send any task name. You can add a check inside the task execution itself to verify a token or permission. This is especially useful when tasks are triggered from external systems (e.g., webhooks).

Create a custom base task class that validates an authentication token passed as a task argument or header.

from celery import Task
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)

class AuthenticatedTask(Task):
    """Base task that requires a valid API token."""
    
    def validate_token(self, token):
        # Replace with your validation logic (e.g., JWT decode, DB lookup)
        # This is a simplified example.
        valid_tokens = {'my-secret-token-123'}
        return token in valid_tokens
    
    def __call__(self, *args, **kwargs):
        # Extract token from kwargs (or from headers if using message protocol)
        token = kwargs.pop('_auth_token', None)
        if not token or not self.validate_token(token):
            raise PermissionError('Invalid or missing authentication token.')
        return super().__call__(*args, **kwargs)

Now, any task that inherits from AuthenticatedTask will require the caller to include _auth_token:

from celery import shared_task

@shared_task(base=AuthenticatedTask, bind=True)
def process_order(self, order_id, **kwargs):
    # Task logic here
    return f'Order {order_id} processed.'

Calling it from a producer:

process_order.delay(order_id=42, _auth_token='my-secret-token-123')

If the token is invalid or missing, the task will raise PermissionError, which can be handled by a custom error handler.

3. Message Signing with Celery Security

Celery provides a built-in security module (celery.security) that uses digital signatures to verify that a task was sent by a trusted source and has not been tampered with. This is an advanced authentication mechanism that uses public-key cryptography.

To enable it, you need to set up cryptographic keys. First, generate a key pair (using OpenSSL):

openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem

Then configure Celery to use the security module:

from celery import Celery
from celery.security import setup_security

app = Celery('tasks', broker='redis://localhost:6379/0')

# Setup security with the private key for signing
setup_security(
    app,
    key='private.pem',
    cert='public.pem',  # optional, used for verification
    # In practice, workers should use the public key to verify.
)

For the producer (sender), use the private key to sign tasks:

# producer.py
from celery import Celery
from celery.security import setup_security

app = Celery('tasks', broker='redis://localhost:6379/0')
setup_security(app, key='private.pem', cert='public.pem')

@app.task
def add(x, y):
    return x + y

# Sending will automatically sign

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles