goauthentik / authentik

The authentication glue you need.
https://goauthentik.io
Other
13.06k stars 868 forks source link

Make branding / customisation of UI more consistent, enhance possibilities #2364

Open agrimpelhuber opened 2 years ago

agrimpelhuber commented 2 years ago

Is your feature request related to a problem? Please describe. The branding / customisation of authentik is somewhat inconsistent. While every flow can be branded in some way (wording, background image), a few things like the URL https://authentik.tld/if/session-end/my-app-name can't, as far as I can see.

Describe the solution you'd like Different stages may be possible:

  1. Make the hard-coded parts of an authentication workflow just as customizable as flows (on tenant level?): Background etc.
  2. Minor thing: As far as I can see, an uploaded background picture for X multiple flows ends up as X picture URLs. Re-use the same
  3. Make a type of branding possible to obscure the fact that the whole system is called "authentik" (e.g. in the above URL, button "go to authentik" is hard-coded). Even though I have no problem with this in my hobbyist use, that might be something to keep people from using Authentik in corporate environments. Therefore, this might be a paid feature (oops!)

Describe alternatives you've considered I might have overlooked something during the long hours I have looked into the customization details. In this case, please point me in the right direction, downgrade this to "question", and I'll share my findings here

benedikt-bartscher commented 2 years ago

I would love to configure this via Django Templates!

BeryJu commented 2 years ago

@agrimpelhuber

  1. Make the hard-coded parts of an authentication workflow just as customizable as flows (on tenant level?): Background etc.

There isn't really much hardcoded during authentication; and while I get having a tenant-level configurable background I think most environments that do change the background just change it to a URL and then update the picture behind that (at least thats how I would do it)

  1. Minor thing: As far as I can see, an uploaded background picture for X multiple flows ends up as X picture URLs. Re-use the same

Yes, this is a downside of how the whole uploading thing works, there could be a UI to show already uploaded files, allthough imo there's more important things before this

  1. Make a type of branding possible to obscure the fact that the whole system is called "authentik" (e.g. in the above URL, button "go to authentik" is hard-coded). Even though I have no problem with this in my hobbyist use, that might be something to keep people from using Authentik in corporate environments. Therefore, this might be a paid feature (oops!)

Yeah the logout screen is currently hardcoded, this will eventually be migrated to also use flows when I finish #2346 and the followup for OIDC, but besides that I recently cleaned up some of the default links, and it only shows Powered by authentik, which is equally hardcoded on things like Okta.

One thing I would like to eventually add for this is custom interface support, i.e. allow people to write their own frontend if they choose to do so, and host it under authentik.tld/if/<some name>/, and with this they'd obviously have full control of what is shown

@benedikt-bartscher authentik doesn't really use django templates for much, there are very few sites that are server-side rendered as most everything of the UI is an SPA nowadays

matmair commented 2 years ago

Hi there @BeryJu thank you for the amazing project! Always nice to see polished Django in the nature. Would you be open to accepting a PR that enables the removal of the branding Powered by authentik via a config / env variable or is this something you would like to keep? If you pay Okta enough they give you full white-labeling including separate servers btw - but it still is not selfhosted and outside of the EU 😇

regbo commented 1 year ago

Hey @matmair ,

If you need a quick fix, this is what I did in my docker-compose file. It's a massive hack, but it removes the powered by links and makes the css apply in more areas:

  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
    user: root
    <<: *authentik-worker-environment
    entrypoint: /bin/bash
    command:
      - -c
      - |
        DIST_DIR=web/dist

        #---CUSTOM BACKGROUND
        URL=https://picsum.photos/1920/1080/?blur&grayscale
        curl -o $$DIST_DIR/assets/images/flow_background.jpg $$URL

        #---CUSTOM_CSS_APPEND
        read -r -d '' CSS << EOM
        input {border-radius: 3px;border: 1px solid transparent;border-top: none;border-bottom: 1px solid #DDD;box-shadow: inset 0 1px 2px rgba(0,0,0,.39), 0 -1px 1px #FFF, 0 1px 0 #FFF;}
        a[href^="https://goauthentik.io"] {display:none !important}
        a[href^="https://unsplash.com"] {display:none !important}
        EOM
        echo "$$CSS" >> $$DIST_DIR/custom.css

        #---CUSTOM_CSS_HEADER
        for FILE in $$DIST_DIR/flow/*; do
            sed -i "s|</header>|<link rel="stylesheet" type="text/css" href="/static/dist/custom.css"></header>|g" $$FILE
        done

        cp -R $$DIST_DIR/* /dist

        /usr/local/bin/dumb-init -- /lifecycle/ak worker
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - geoip:/geoip
      - certs:/certs
      - custom-templates:/templates
      - media:/media
      - dist:/dist

NOTE: This only works because I have my worker and server sharing a volume. The worker (running as root), modifies the files, and then copies them to the shared /dist folder on every boot. It then adds a link to the custom css where possible using sed in the template files.

matmair commented 1 year ago

Thanks @regbo, I have a similar solution. Mainly asked to get the go-ahead to address this upstream if there was interest from the maintainer but it does not seem so - which I respect.

Eisfunke commented 1 year ago

Yes, this is a downside of how the whole uploading thing works, there could be a UI to show already uploaded files, allthough imo there's more important things before this

The easiest way would probably be to allow free text entry even if a /media folder is mounted. Then I could upload a picture once using the upload button and local file selector and then just paste the same web path in other places, such as flow backgrounds.

BTW, an idea for other people who want to use a custom background without uploading the same file multiple times:

I worked around the image doubling issue by just not mounting /media and instead mounting an external folder to /web/dist/extra in the container. I can then manually upload files to that folder from the host. That folder is served to /static/dist/extra.

As /media isn't mounted, I get free text fields for media to enter any URL. E.g. for flow backgrounds I can then enter something like /static/dist/extra/mypicture.jpg. Now I can reuse that link without uploading the same picture again and again.

That's a little more work and authentik doesn't detect a different picture is being used and still shows the "Background image" link to unsplash – even though that image isn't used anymore. But I can live with that.

regbo commented 1 year ago

@matmair and @Eisfunke ,

I simplified a lot of this by creating the following:

https://github.com/regbo/public-html/tree/master/authentik

It assumes that /dist is mounted on the worker, and that same volume is mounted to /web/dist on the servers. To use it, replace the entrypoint and command with this on the worker:

    entrypoint: /bin/bash
    command:
      - -c
      - |
        curl -fsSL https://raw.githubusercontent.com/regbo/public-html/master/authentik/run-worker.sh | bash

The environment variable 'AUTHENTIK_FLOW_BACKGROUND_URL' can be used to set a background. Also, it auto injects .js and .css files by reading 'AUTHENTIK_INJECTURL*" environment variables. CSS files are injected into all doms, including the shadow roots. So for example, this is what I use in my docker config:

    environment:
      AUTHENTIK_FLOW_BACKGROUND_URL: ${AUTHENTIK_FLOW_BACKGROUND_URL:-https://raw.githubusercontent.com/regbo/public-html/master/background.png}
      AUTHENTIK_INJECT_URL_0: https://rawcdn.githack.com/regbo/public-html/9dd48564ee00433ceced7177c6376731899e8147/authentik/input.css
      AUTHENTIK_INJECT_URL_1: https://rawcdn.githack.com/regbo/public-html/444807d39c657ffef12ae1a8ea882ed7667eca3e/authentik/remove-branding.js

The above allows me to externalize some of the styling.

rroblik commented 1 year ago

Authentik lack of white labeling / customization 🤨

nothing serious seems to be on the roadmap against that isn't it @BeryJu ?

pupazze commented 1 year ago

Hi @regbo, I can't get your custumization working, my docker-compose.yml looks like this:

version: '3.4'

services:
  postgresql:
    image: docker.io/library/postgres:12-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${PG_PASS:?database password required}
      - POSTGRES_USER=${PG_USER:-authentik}
      - POSTGRES_DB=${PG_DB:-authentik}
    env_file:
      - .env
  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redis:/data
  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.2}
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
    env_file:
      - .env
    ports:
      - "${AUTHENTIK_PORT_HTTP:-9000}:9000"
      - "${AUTHENTIK_PORT_HTTPS:-9443}:9443"
  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2023.1.2}
    restart: unless-stopped
    entrypoint: /bin/bash
    command:
      - -c
      - |
        curl -fsSL https://raw.githubusercontent.com/regbo/public-html/master/authentik/run-worker.sh | bash
    environment:
      AUTHENTIK_FLOW_BACKGROUND_URL: ${AUTHENTIK_FLOW_BACKGROUND_URL:-https://raw.githubusercontent.com/regbo/public-html/master/background.png}
      AUTHENTIK_INJECT_URL_0: https://rawcdn.githack.com/regbo/public-html/9dd48564ee00433ceced7177c6376731899e8147/authentik/input.css
      AUTHENTIK_INJECT_URL_1: https://rawcdn.githack.com/regbo/public-html/444807d39c657ffef12ae1a8ea882ed7667eca3e/authentik/remove-branding.js
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
    # `user: root` and the docker socket volume are optional.
    # See more for the docker socket integration here:
    # https://goauthentik.io/docs/outposts/integrations/docker
    # Removing `user: root` also prevents the worker from fixing the permissions
    # on the mounted folders, so when removing this make sure the folders have the correct UID/GID
    # (1000:1000 by default)
    user: root
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./media:/media
      - ./certs:/certs
      - ./custom-templates:/templates
    env_file:
      - .env

volumes:
  database:
    driver: local
  redis:
    driver: local

did I missed something or did I pasted it to the wrong place?

Thanks in advanced pupazze

pupazze commented 1 year ago

for all who are also interessted in this, I still got no success with my customised docker-compose.yml file, but the solution working for me is nevertheless found in the details @regbo posted:

for FILE in /web/dist/flow/*; do
     sed -i "s|</header>|<link rel="stylesheet" type="text/css" href="/static/dist/custom.css"></header>|g" $FILE
done

this bash part makes it possible for me to apply my custom css as wanted

rroblik commented 1 year ago

So so so dirty hack 😮‍💨 I've switched back to Keycloak

regbo commented 1 year ago

Hey all,

Sorry for the delay. This is indeed a dirty nasty hack. But, FWIW, below is my compose file. The key is to make the server share the same dist directory as the worker instance. It won't work if they don't share a common volume across all instances (in my case I'm using gluster):

---
version: '3.4'

x-authentik-env: &authentik-env
      AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY?Variable not set}

      AUTHENTIK_POSTGRESQL__HOST: ${AUTHENTIK_POSTGRESQL__HOST?Variable not set}
      AUTHENTIK_POSTGRESQL__PORT: ${AUTHENTIK_POSTGRESQL__PORT:-5432}
      AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_POSTGRESQL__NAME?Variable not set}
      AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_POSTGRESQL__USER?Variable not set}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL__PASSWORD?Variable not set}
      AUTHENTIK_POSTGRESQL__USE_PGBOUNCER: ${AUTHENTIK_POSTGRESQL__USE_PGBOUNCER:-False}

      AUTHENTIK_EMAIL__HOST: postfix
      AUTHENTIK_EMAIL__PORT: 25
      AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_EMAIL__FROM_NAME} <auth@${DOMAIN?Variable not set}>

      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_AUTHENTIK__GEOIP: /geoip/GeoLite2-City.mmdb
      AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
      AUTHENTIK_FOOTER_LINKS: "[]"

x-authentik-server-environment: &authentik-server-environment
    environment:
      <<: *authentik-env

x-authentik-worker-environment: &authentik-worker-environment
    environment:
      <<: *authentik-env
      AUTHENTIK_FLOW_BACKGROUND_URL: ${AUTHENTIK_FLOW_BACKGROUND_URL:-https://raw.githubusercontent.com/regbo/public-html/master/authentik/background.png}
      AUTHENTIK_BRAND_ICON_URL: ${AUTHENTIK_BRAND_ICON_URL?Variable nto set}
      AUTHENTIK_INJECT_CSS_URL_0: https://raw.githubusercontent.com/regbo/public-html/master/authentik/input.css
      AUTHENTIK_INJECT_CSS_URL_1: https://rawcdn.githack.com/regbo/public-html/2561d77f76d55b9f91afa697f2570f6ed74e6b94/authentik/hide-branding.css

services:

  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redis:/data
    networks:
      authentik:
        aliases: [redis]
    logging:
      driver: "json-file"
      options:
          max-file: 5
          max-size: 10m
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: any
      placement:
        constraints: [node.role == manager]

  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
    hostname: authentik-server
    <<: *authentik-server-environment
    command: server
    volumes:
      - geoip:/geoip
      - certs:/certs
      - custom-templates:/templates
      - media:/media
      - dist:/web/dist
    networks:
      traefik-public: {}
      authentik:
        aliases: [authentik-server]
    logging:
      driver: "json-file"
      options:
          max-file: 5
          max-size: 10m
    deploy:
      mode: global
      restart_policy:
        condition: any
      placement:
        constraints: [node.role == manager]
      labels:
        #required or treafik may select the correct ip
        - traefik.docker.network=traefik-public
        - traefik.enable=true
        ## Individual Application forwardAuth regex (catch any subdomain using individual application forwardAuth)
        - traefik.http.routers.authentik-rtr-outpost.rule=Path(`/outpost.goauthentik.io`) || PathPrefix(`/outpost.goauthentik.io/`)
        - traefik.http.routers.authentik-rtr-outpost.entrypoints=tcps
        - traefik.http.routers.authentik-rtr-outpost.tls=true
        - traefik.http.routers.authentik-rtr-outpost.priority=9223372036854775807
        - traefik.http.routers.authentik-rtr-outpost.middlewares=authentik-mdw-outpost
        - traefik.http.middlewares.authentik-mdw-outpost.headers.customresponseheaders.X-Authentik-Outpost=true
        ## HTTP Routers
        - traefik.http.routers.authentik-rtr.rule=HostRegexp(`auth.{domain:\S+}`)
        - traefik.http.routers.authentik-rtr.entrypoints=tcps
        - traefik.http.routers.authentik-rtr.tls=true
        # `authentik-server` refers to the service name in the compose file.
        - traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik
        - traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true
        - traefik.http.middlewares.authentik.forwardauth.authResponseHeadersRegex=^X-Authentik-
        ## HTTP Services
        - traefik.http.routers.authentik-rtr.service=authentik-svc
        - traefik.http.services.authentik-svc.loadBalancer.server.port=9000
        - traefik.http.services.authentik-svc.loadBalancer.sticky.cookie=true
        - traefik.http.services.authentik-svc.loadBalancer.sticky.cookie.name=authentik_lb

  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-latest}
    user: root
    <<: *authentik-worker-environment
    entrypoint: /bin/bash
    command:
      - -c
      - |
        command -v jq >/dev/null 2>&1 || { echo "installing jq"; curl -fsSL https://glare.vercel.app/stedolan/jq/linux64 -o /usr/bin/jq; chmod +x /usr/bin/jq; }
        echo "starting worker";
        curl -fsSL https://api.github.com/repos/regbo/public-html/contents/authentik/run-worker.sh | jq -r ".content" | base64 --decode | bash
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - geoip:/geoip
      - certs:/certs
      - custom-templates:/templates
      - media:/media
      - dist:/dist
    networks:
      authentik: {}
    logging:
      driver: "json-file"
      options:
          max-file: 5
          max-size: 10m
    healthcheck:
      test: ["CMD", "/lifecycle/ak", "healthcheck"]
      interval: 30s
      timeout: 30s
      start_period: 60s
      retries: 10
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

  geoipupdate:
    image: "maxmindinc/geoipupdate:latest"
    volumes:
      - "geoip:/usr/share/GeoIP"
    environment:
      GEOIPUPDATE_ACCOUNT_ID: ${GEOIPUPDATE_ACCOUNT_ID?Variable not set}
      GEOIPUPDATE_LICENSE_KEY: ${GEOIPUPDATE_LICENSE_KEY?Variable not set}
      GEOIPUPDATE_EDITION_IDS: "GeoLite2-City"
      GEOIPUPDATE_FREQUENCY: "8"
    logging:
      driver: "json-file"
      options:
          max-file: 5
          max-size: 10m
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints: [node.role == manager]

  postfix:
    image: juanluisbaptiste/postfix:latest
    environment:
      SMTP_SERVER: ${POSTFIX_SMTP_HOST?Variable not set}
      SMTP_PORT: ${POSTFIX_SMTP_PORT:-587}
      SMTP_USERNAME: ${POSTFIX_SMTP_USERNAME}
      SMTP_PASSWORD: ${POSTFIX_SMTP_PASSWORD}
      SERVER_HOSTNAME: ${DOMAIN?Variable not set}
      LOG_SUBJECT: "true"
      DEBUG: ${POSTFIX_DEBUG:-false}
    networks:
      authentik:
        aliases: [postfix]
    logging:
      driver: "json-file"
      options:
          max-file: 5
          max-size: 10m
    deploy:
      mode: replicated
      replicas: 1
      restart_policy:
        condition: on-failure
      placement:
        constraints:
          - ${POSTFIX_DEPLOY_PLACEMENT_CONSTRAINTS:-node.role == manager}

volumes:
  redis:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/redis"
  geoip:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/geoip"
  certs:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/certs"
  custom-templates:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/custom-templates"
  media:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/media"
  dist:
    driver: "local"
    driver_opts:
      type: "none"
      o: "bind"
      device: "/mnt/gluster/authentik/dist"

networks:
  authentik: {}
  traefik-public:
    external: true
a3linux commented 1 year ago

I want to know if I can use docker mount volume to replace the flow_background.jpg and branding logo, sidebar icon directly? Don't care about the footer links and others?

sevmonster commented 1 year ago

I want to know if I can use docker mount volume to replace the flow_background.jpg and branding logo, sidebar icon directly? Don't care about the footer links and others?

I do this in my environment:

- /myimg.jpg:/web/dist/assets/images/flow_background.jpg

It might be cached, so do a hard refresh after changing.

sevmonster commented 1 year ago

This might be closed by #4804.

@regbo @pupazze Does this solve your situation so you don't have to futz so much with the container? I have had great success using pure CSS so far. Specifically on updating the background: https://github.com/goauthentik/authentik/issues/4586#issuecomment-1452051178

ne0YT commented 1 year ago

were you smh able to make the cards rounded with a custom.css?

sevmonster commented 1 year ago

https://github.com/goauthentik/authentik/discussions/4831

ne0YT commented 1 year ago

4831

youre not on "2023.2.3" right? stuff like this is still wrong for me (the orange part): image

sevmonster commented 1 year ago

You need to update to gh-main (step three) or the PR branch until #4804 lands in a release.

BeryJu commented 1 year ago

We'll be finalising #5048 hopefully soon-ish, the whitelabelling will be an option for enterprise licenses. For the file upload, as a relatively simple option, we're considering giving people that run authentik in docker-compose the option to set files to URLs (which is the default behaviour when no persistent storage is mounted, like on K8s), which would slightly improve this

septatrix commented 1 year ago

It would also be great if the tenant logo input would have the same file-picker logic as application logos have (i.e. show a file-picker if a volume is mounted under /media and also offer the possibility to revert back to the default again)

septatrix commented 1 year ago

Another bug-ish issue is that Authentik offers to delete images even when once under /dist/static are referenced which it definitely should not. In general media/file-input handling seems like it would benefit from a bit of a cleanup, either making it more powerful (separate media section similar to certificates, fa-icon overview etc.) or keeping it simpler but avoiding inconsistencies (get rid of the delete option, only always offer text field etc.).

almereyda commented 1 year ago

As a heads up to @BeryJu comment above, it appears setting URLs, e.g. for a Tenant's Logo or Favicon, is now also possible when /media is mounted into the 2023.8.1 container.

BeryJu commented 6 months ago

To update this issue:

nootsponge commented 1 month ago

I'd like to add that, if possible, this function should be collecting the brand's logo path instead of the default logo's path. Additionally, the email stuff should overall support branding better as it stands with the event log even reflecting "Brand fallback" no matter the configuration.

Edit: I'll add the feature if anyone wants to point me in the right direction.