DEV Community

Timo Reusch
Timo Reusch

Posted on

Federated avatars in authentik

By default, authentik shows the users initials as an avatar or pulls the profile picture from Gravatar (if one is available).
However, unfortunately it does not offer users to set avatars directly in the interface. In this article (based on this GitHub discussion), we will add this functionality.

Tested with authentik 2025.10.0

1. Create a folder to store your avatars

Avatar image files will be stored in a sub-directory /media/user-avatars/ Let's create this:

mkdir -p /authentik/media/user-avatars/
Enter fullscreen mode Exit fullscreen mode

Make sure the directory is writable by authentik, e.g.:

chown -R 1000:1000 /authentik/media/user-avatars/
chmod -R 644 /authentik/media/user-avatars/
Enter fullscreen mode Exit fullscreen mode

2. Create the prompts

This is where we define the fields for uploading an image and deleting it.

  1. Head to Administration SettingsFlows and StagesPrompts
  2. Create a new file-prompt:
    • Name: e.g. default-user-settings-field-avatar
    • Field Key: attributes.avatar
    • Label (how its shown to the user in the UI): e.g. User Avatar
    • Type: File
    • Order: e.g. 205
  3. Create a new checkbox-prompt
    • Name: e.g. default-user-settings-field-avatar-reset
    • Field Key: attributes.avatar_reset
    • Label: e.g. Delete Avatar
    • Type: Checkbox
    • Order: e.g. 206

3. Create a custom expression policy

This is where the uploaded file is saved, any previous file is removed, and the new URL is prepared to be stored in the user profile.

  1. Head to Administration SettingsCustomizationPolicies
  2. Click on Create
  3. Select Expression Policy
  4. Name it e.g.
  5. Paste the following expression. Make sure to set your AK_DOMAIN (and optionally FILE_PATH in case you changed it). You can also adjust MAX_UPLOAD_SIZE and ACCEPTED_FILE_TYPES:
AK_DOMAIN = "authentik.company"
FILE_PATH = "/media/user-avatars/"
URL_PATH = f"https://{AK_DOMAIN}{FILE_PATH}"
MAX_UPLOAD_SIZE = 5 * 1024 * 1024
ACCEPTED_FILE_TYPES = {
  "image/png": "png",
  "image/jpeg": "jpeg",
  "image/webp": "webp",
  # "image/svg+xml": "svg", # if you trust your users not to upload malicious svg files
  "image/gif": "gif",
  "image/bmp": "bmp",
  "image/vnd.microsoft.icon": "ico"
}

EMPTY_FILE = "data:application/octet-stream;base64,"

# from humanize import naturalsize
from uuid import uuid4
from base64 import b64decode
from os import remove
from os.path import isfile

def naturalsize(value, binary=False, format="%.1f"):
    """
    Convert a size in bytes into a human-readable format.

    :param value: size in bytes
    :param binary: if True, use 1024-based units (KiB, MiB), otherwise 1000-based (KB, MB)
    :param format: number format string (default "%.1f")
    :return: string like "10.5 MB"
    """
    units = ["B", "KB", "MB", "GB", "TB", "PB", "EB"]
    if binary:
        step = 1024.0
        units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]
    else:
        step = 1000.0

    size = float(value)
    for unit in units:
        if abs(size) < step:
            return (format % size).rstrip("0").rstrip(".") + " " + unit
        size /= step
    return (format % size).rstrip("0").rstrip(".") + " " + units[-1]

def remove_old_avatar_file():
  if not request.user.is_authenticated:
    return
  avatar = request.user.attributes.get("avatar", None)
  if avatar:
    components = avatar.split(URL_PATH, 1)
    if len(components) == 2 and components[0] == "" and components[1]:
      old_filename = FILE_PATH + components[1]
      if isfile(old_filename):
        remove(old_filename)

prompt_data = request.context.get("prompt_data")
avatar_overwritten = False

if "avatar" in prompt_data.get("attributes", {}):
  avatar = prompt_data["attributes"]["avatar"]
  if avatar == EMPTY_FILE:
    # No upload file specified, ignore
    del prompt_data["attributes"]["avatar"]
  else:
    avatar_mimetype = avatar.split("data:", 1)[1].split(";", 1)[0]
    if avatar_mimetype not in ACCEPTED_FILE_TYPES:
      ak_message("User avatar must be an image (" + ", ".join(ACCEPTED_FILE_TYPES.values()) + ") file.")
      return False
    # Now we know it is one of the accepted image file types
    avatar_base64 = avatar.split(",", 1)[1]
    avatar_binary = b64decode(avatar_base64)
    avatar_size = len(avatar_binary)
    if avatar_size > MAX_UPLOAD_SIZE:
      ak_message("User avatar file size must not exceed " + naturalsize(MAX_UPLOAD_SIZE, binary = True, format = "%.0f") + ".")
      return False
    # Set a random file name with extension reflecting mime type
    avatar_filename = str(uuid4()) + "." + ACCEPTED_FILE_TYPES[avatar_mimetype]
    try:
      with (open(FILE_PATH + avatar_filename, "wb") as f):
        f.write(avatar_binary)
    except:
      ak_message("Could not write avatar file.")
      return False
    avatar_overwritten = True
    remove_old_avatar_file()
    prompt_data["attributes"]["avatar"] = URL_PATH + avatar_filename

if "avatar_reset" in prompt_data.get("attributes", {}):
  del prompt_data["attributes"]["avatar_reset"]
  # If a new avatar was uploaded, previous avatar is deleted anyway, so ignore
  if not avatar_overwritten:
    prompt_data["attributes"]["avatar"] = None
    remove_old_avatar_file()
    ak_message("Deleted user avatar.")

return True
Enter fullscreen mode Exit fullscreen mode

4. Modify the user prompt stage

  1. Head to Administration SettingsFlows and StagesStages
  2. Select the default-user-settings PromptStage (or create a new, custom prompt stage, if you prefer)
  3. Add default-user-settings-field-avatar and default-user-settings-field-avatar-reset to the Fields section (alongside the default values).
  4. Add default-user-settings-avatar-authorization to the Validation Policies (alongside the default values).

5. Enable attributes.avatar

  1. Head to Administration SettingsSystemSettings
  2. For Avatars, set the value to attributes.avatar,gravatar,initials. You can change order if you like (in this example gravatar is the fallback, if attributes.avatar=None and initials if gravatar is also none.

6. Test it out!

Go to your user profile settings if/user/#/settings. You should now see the fields we just created and be able to upload and delete your profile picture! When refreshing the site, it will be displayed in the top right where your initials used to be.

You can double-check the media/user-avatars/ folder on your server.

Top comments (0)