"""Tasks API"""

from importlib import import_module

from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import (
    CharField,
    ChoiceField,
    DateTimeField,
    FloatField,
    SerializerMethodField,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
from structlog.stdlib import get_logger

from authentik.events.logs import LogEventSerializer
from authentik.events.models import SystemTask, TaskStatus
from authentik.rbac.decorators import permission_required

LOGGER = get_logger()


class SystemTaskSerializer(ModelSerializer):
    """Serialize TaskInfo and TaskResult"""

    name = CharField()
    full_name = SerializerMethodField()
    uid = CharField(required=False)
    description = CharField()
    start_timestamp = DateTimeField(read_only=True)
    finish_timestamp = DateTimeField(read_only=True)
    duration = FloatField(read_only=True)

    status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus])
    messages = LogEventSerializer(many=True)

    def get_full_name(self, instance: SystemTask) -> str:
        """Get full name with UID"""
        if instance.uid:
            return f"{instance.name}:{instance.uid}"
        return instance.name

    class Meta:
        model = SystemTask
        fields = [
            "uuid",
            "name",
            "full_name",
            "uid",
            "description",
            "start_timestamp",
            "finish_timestamp",
            "duration",
            "status",
            "messages",
        ]


class SystemTaskViewSet(ReadOnlyModelViewSet):
    """Read-only view set that returns all background tasks"""

    queryset = SystemTask.objects.all()
    serializer_class = SystemTaskSerializer
    filterset_fields = ["name", "uid", "status"]
    ordering = ["name", "uid", "status"]
    search_fields = ["name", "description", "uid", "status"]

    @permission_required(None, ["authentik_events.run_task"])
    @extend_schema(
        request=OpenApiTypes.NONE,
        responses={
            204: OpenApiResponse(description="Task retried successfully"),
            404: OpenApiResponse(description="Task not found"),
            500: OpenApiResponse(description="Failed to retry task"),
        },
    )
    @action(detail=True, methods=["POST"], permission_classes=[])
    def run(self, request: Request, pk=None) -> Response:
        """Run task"""
        task: SystemTask = self.get_object()
        try:
            task_module = import_module(task.task_call_module)
            task_func = getattr(task_module, task.task_call_func)
            LOGGER.info("Running task", task=task_func)
            task_func.delay(*task.task_call_args, **task.task_call_kwargs)
            messages.success(
                self.request,
                _("Successfully started task {name}.".format_map({"name": task.name})),
            )
            return Response(status=204)
        except (ImportError, AttributeError) as exc:  # pragma: no cover
            LOGGER.warning("Failed to run task, remove state", task=task.name, exc=exc)
            # if we get an import error, the module path has probably changed
            task.delete()
            return Response(status=500)

authentik/events/api/tasks.py
"""Tasks API"""

from importlib import import_module

from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import (
    CharField,
    ChoiceField,
    DateTimeField,
    FloatField,
    SerializerMethodField,
)
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ReadOnlyModelViewSet
from structlog.stdlib import get_logger

from authentik.events.logs import LogEventSerializer
from authentik.events.models import SystemTask, TaskStatus
from authentik.rbac.decorators import permission_required

LOGGER = get_logger()


class SystemTaskSerializer(ModelSerializer):
    """Serialize TaskInfo and TaskResult"""

    name = CharField()
    full_name = SerializerMethodField()
    uid = CharField(required=False)
    description = CharField()
    start_timestamp = DateTimeField(read_only=True)
    finish_timestamp = DateTimeField(read_only=True)
    duration = FloatField(read_only=True)

    status = ChoiceField(choices=[(x.value, x.name) for x in TaskStatus])
    messages = LogEventSerializer(many=True)

    def get_full_name(self, instance: SystemTask) -> str:
        """Get full name with UID"""
        if instance.uid:
            return f"{instance.name}:{instance.uid}"
        return instance.name

    class Meta:
        model = SystemTask
        fields = [
            "uuid",
            "name",
            "full_name",
            "uid",
            "description",
            "start_timestamp",
            "finish_timestamp",
            "duration",
            "status",
            "messages",
            "expires",
            "expiring",
        ]


class SystemTaskViewSet(ReadOnlyModelViewSet):
    """Read-only view set that returns all background tasks"""

    queryset = SystemTask.objects.all()
    serializer_class = SystemTaskSerializer
    filterset_fields = ["name", "uid", "status"]
    ordering = ["name", "uid", "status"]
    search_fields = ["name", "description", "uid", "status"]

    @permission_required(None, ["authentik_events.run_task"])
    @extend_schema(
        request=OpenApiTypes.NONE,
        responses={
            204: OpenApiResponse(description="Task retried successfully"),
            404: OpenApiResponse(description="Task not found"),
            500: OpenApiResponse(description="Failed to retry task"),
        },
    )
    @action(detail=True, methods=["POST"], permission_classes=[])
    def run(self, request: Request, pk=None) -> Response:
        """Run task"""
        task: SystemTask = self.get_object()
        try:
            task_module = import_module(task.task_call_module)
            task_func = getattr(task_module, task.task_call_func)
            LOGGER.info("Running task", task=task_func)
            task_func.delay(*task.task_call_args, **task.task_call_kwargs)
            messages.success(
                self.request,
                _("Successfully started task {name}.".format_map({"name": task.name})),
            )
            return Response(status=204)
        except (ImportError, AttributeError) as exc:  # pragma: no cover
            LOGGER.warning("Failed to run task, remove state", task=task.name, exc=exc)
            # if we get an import error, the module path has probably changed
            task.delete()
            return Response(status=500)

authentik/events/api/tasks.py
diff --git a/authentik/events/api/tasks.py b/authentik/events/api/tasks.py
index 09cec8457e33..529464ad5ff5 100644
--- a/authentik/events/api/tasks.py
+++ b/authentik/events/api/tasks.py
@@ -60,6 +60,8 @@ class Meta:
             "duration",
             "status",
             "messages",
+            "expires",
+            "expiring",
         ]
 
 
diff --git a/go.mod b/go.mod
index e125cb439726..7fcb06afea73 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/go-ldap/ldap/v3 v3.4.8
 	github.com/go-openapi/runtime v0.28.0
 	github.com/go-openapi/strfmt v0.23.0
-	github.com/golang-jwt/jwt v3.2.2+incompatible
+	github.com/golang-jwt/jwt/v5 v5.2.1
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
diff --git a/go.sum b/go.sum
index fcd850c7ae57..adddb6d763c4 100644
--- a/go.sum
+++ b/go.sum
@@ -111,8 +111,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
 github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
 github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
 github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
-github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
-github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
diff --git a/internal/outpost/proxyv2/application/application.go b/internal/outpost/proxyv2/application/application.go
index 3f3fcaa3b665..0a308280da61 100644
--- a/internal/outpost/proxyv2/application/application.go
+++ b/internal/outpost/proxyv2/application/application.go
@@ -192,7 +192,9 @@ func NewApplication(p api.ProxyOutpostConfig, c *http.Client, server Server) (*A
 		})
 	})
 
-	mux.HandleFunc("/outpost.goauthentik.io/start", a.handleAuthStart)
+	mux.HandleFunc("/outpost.goauthentik.io/start", func(w http.ResponseWriter, r *http.Request) {
+		a.handleAuthStart(w, r, "")
+	})
 	mux.HandleFunc("/outpost.goauthentik.io/callback", a.handleAuthCallback)
 	mux.HandleFunc("/outpost.goauthentik.io/sign_out", a.handleSignOut)
 	switch *p.Mode {
diff --git a/internal/outpost/proxyv2/application/mode_forward.go b/internal/outpost/proxyv2/application/mode_forward.go
index 47b5dd47b186..4a2c987d9e5e 100644
--- a/internal/outpost/proxyv2/application/mode_forward.go
+++ b/internal/outpost/proxyv2/application/mode_forward.go
@@ -59,19 +59,11 @@ func (a *Application) forwardHandleTraefik(rw http.ResponseWriter, r *http.Reque
 		a.log.Trace("path can be accessed without authentication")
 		return
 	}
-	a.handleAuthStart(rw, r)
 	// set the redirect flag to the current URL we have, since we redirect
 	// to a (possibly) different domain, but we want to be redirected back
 	// to the application
 	// X-Forwarded-Uri is only the path, so we need to build the entire URL
-	s, _ := a.sessions.Get(r, a.SessionName())
-	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
-		s.Values[constants.SessionRedirect] = fwd.String()
-		err = s.Save(r, rw)
-		if err != nil {
-			a.log.WithError(err).Warning("failed to save session")
-		}
-	}
+	a.handleAuthStart(rw, r, fwd.String())
 }
 
 func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request) {
@@ -110,19 +102,11 @@ func (a *Application) forwardHandleCaddy(rw http.ResponseWriter, r *http.Request
 		a.log.Trace("path can be accessed without authentication")
 		return
 	}
-	a.handleAuthStart(rw, r)
 	// set the redirect flag to the current URL we have, since we redirect
 	// to a (possibly) different domain, but we want to be redirected back
 	// to the application
 	// X-Forwarded-Uri is only the path, so we need to build the entire URL
-	s, _ := a.sessions.Get(r, a.SessionName())
-	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
-		s.Values[constants.SessionRedirect] = fwd.String()
-		err = s.Save(r, rw)
-		if err != nil {
-			a.log.WithError(err).Warning("failed to save session")
-		}
-	}
+	a.handleAuthStart(rw, r, fwd.String())
 }
 
 func (a *Application) forwardHandleNginx(rw http.ResponseWriter, r *http.Request) {
@@ -185,17 +169,9 @@ func (a *Application) forwardHandleEnvoy(rw http.ResponseWriter, r *http.Request
 		a.log.Trace("path can be accessed without authentication")
 		return
 	}
-	a.handleAuthStart(rw, r)
 	// set the redirect flag to the current URL we have, since we redirect
 	// to a (possibly) different domain, but we want to be redirected back
 	// to the application
 	// X-Forwarded-Uri is only the path, so we need to build the entire URL
-	s, _ := a.sessions.Get(r, a.SessionName())
-	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
-		s.Values[constants.SessionRedirect] = fwd.String()
-		err = s.Save(r, rw)
-		if err != nil {
-			a.log.WithError(err).Warning("failed to save session before redirect")
-		}
-	}
+	a.handleAuthStart(rw, r, fwd.String())
 }
diff --git a/internal/outpost/proxyv2/application/mode_forward_caddy_test.go b/internal/outpost/proxyv2/application/mode_forward_caddy_test.go
index c2994f7dac56..6e0e860e0e9f 100644
--- a/internal/outpost/proxyv2/application/mode_forward_caddy_test.go
+++ b/internal/outpost/proxyv2/application/mode_forward_caddy_test.go
@@ -47,16 +47,14 @@ func TestForwardHandleCaddy_Single_Headers(t *testing.T) {
 	a.forwardHandleCaddy(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://test.goauthentik.io/app", st.Redirect)
 }
 
 func TestForwardHandleCaddy_Single_Claims(t *testing.T) {
@@ -134,14 +132,12 @@ func TestForwardHandleCaddy_Domain_Header(t *testing.T) {
 	a.forwardHandleCaddy(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://test.goauthentik.io/app", st.Redirect)
 }
diff --git a/internal/outpost/proxyv2/application/mode_forward_envoy_test.go b/internal/outpost/proxyv2/application/mode_forward_envoy_test.go
index a8b2abfa009c..dbb8b5412fcf 100644
--- a/internal/outpost/proxyv2/application/mode_forward_envoy_test.go
+++ b/internal/outpost/proxyv2/application/mode_forward_envoy_test.go
@@ -32,16 +32,14 @@ func TestForwardHandleEnvoy_Single_Headers(t *testing.T) {
 	a.forwardHandleEnvoy(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://ext.t.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://ext.t.goauthentik.io/app", st.Redirect)
 }
 
 func TestForwardHandleEnvoy_Single_Claims(t *testing.T) {
@@ -102,15 +100,13 @@ func TestForwardHandleEnvoy_Domain_Header(t *testing.T) {
 	a.forwardHandleEnvoy(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://test.goauthentik.io/app", st.Redirect)
 }
diff --git a/internal/outpost/proxyv2/application/mode_forward_traefik_test.go b/internal/outpost/proxyv2/application/mode_forward_traefik_test.go
index 227dc1cb5220..a6b8963a835d 100644
--- a/internal/outpost/proxyv2/application/mode_forward_traefik_test.go
+++ b/internal/outpost/proxyv2/application/mode_forward_traefik_test.go
@@ -47,16 +47,14 @@ func TestForwardHandleTraefik_Single_Headers(t *testing.T) {
 	a.forwardHandleTraefik(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://test.goauthentik.io/app", st.Redirect)
 }
 
 func TestForwardHandleTraefik_Single_Claims(t *testing.T) {
@@ -134,14 +132,12 @@ func TestForwardHandleTraefik_Domain_Header(t *testing.T) {
 	a.forwardHandleTraefik(rr, req)
 
 	assert.Equal(t, http.StatusFound, rr.Code)
-	loc, _ := rr.Result().Location()
-	s, _ := a.sessions.Get(req, a.SessionName())
+	loc, st := a.assertState(t, req, rr)
 	shouldUrl := url.Values{
 		"client_id":     []string{*a.proxyConfig.ClientId},
 		"redirect_uri":  []string{"https://ext.t.goauthentik.io/outpost.goauthentik.io/callback?X-authentik-auth-callback=true"},
 		"response_type": []string{"code"},
-		"state":         []string{s.Values[constants.SessionOAuthState].(string)},
 	}
 	assert.Equal(t, fmt.Sprintf("http://fake-auth.t.goauthentik.io/auth?%s", shouldUrl.Encode()), loc.String())
-	assert.Equal(t, "http://test.goauthentik.io/app", s.Values[constants.SessionRedirect])
+	assert.Equal(t, "http://test.goauthentik.io/app", st.Redirect)
 }
diff --git a/internal/outpost/proxyv2/application/oauth.go b/internal/outpost/proxyv2/application/oauth.go
index 9f9c18d8f273..f2dc93319d25 100644
--- a/internal/outpost/proxyv2/application/oauth.go
+++ b/internal/outpost/proxyv2/application/oauth.go
@@ -1,13 +1,10 @@
 package application
 
 import (
-	"encoding/base64"
 	"net/http"
 	"net/url"
 	"strings"
-	"time"
 
-	"github.com/gorilla/securecookie"
 	"goauthentik.io/api/v3"
 	"goauthentik.io/internal/outpost/proxyv2/constants"
 )
@@ -48,69 +45,59 @@ func (a *Application) checkRedirectParam(r *http.Request) (string, bool) {
 	return u.String(), true
 }
 
-func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request) {
-	newState := base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
-	s, _ := a.sessions.Get(r, a.SessionName())
-	// Check if we already have a state in the session,
-	// and if we do we don't do anything here
-	currentState, ok := s.Values[constants.SessionOAuthState].(string)
-	if ok {
-		claims, err := a.checkAuth(rw, r)
-		if err != nil && claims != nil {
-			a.log.Trace("auth start request with existing authenticated session")
-			a.redirect(rw, r)
-			return
-		}
-		a.log.Trace("session already has state, sending redirect to current state")
-		http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(currentState), http.StatusFound)
+func (a *Application) handleAuthStart(rw http.ResponseWriter, r *http.Request, fwd string) {
+	state, err := a.createState(r, fwd)
+	if err != nil {
+		a.log.WithError(err).Warning("failed to create state")
 		return
 	}
-	rd, ok := a.checkRedirectParam(r)
-	if ok {
-		s.Values[constants.SessionRedirect] = rd
-		a.log.WithField("rd", rd).Trace("Setting redirect")
-	}
-	s.Values[constants.SessionOAuthState] = newState
-	err := s.Save(r, rw)
+	s, _ := a.sessions.Get(r, a.SessionName())
+	err = s.Save(r, rw)
 	if err != nil {
 		a.log.WithError(err).Warning("failed to save session")
 	}
-	http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(newState), http.StatusFound)
+	http.Redirect(rw, r, a.oauthConfig.AuthCodeURL(state), http.StatusFound)
 }
 
-func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) {
+func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
 	s, err := a.sessions.Get(r, a.SessionName())
 	if err != nil {
-		a.log.WithError(err).Trace("failed to get session")
+		a.log.WithError(err).Warning("failed to decode session")
 	}
-	state, ok := s.Values[constants.SessionOAuthState]
-	if !ok {
-		a.log.Warning("No state saved in session")
-		a.redirect(rw, r)
-		return
+	if r.Header.Get(constants.HeaderAuthorization) != "" && *a.proxyConfig.InterceptHeaderAuth {
+		rw.WriteHeader(401)
+		er := a.errorTemplates.Execute(rw, ErrorPageData{
+			Title:       "Unauthenticated",
+			Message:     "Due to 'Receive header authentication' being set, no redirect is performed.",
+			ProxyPrefix: "/outpost.goauthentik.io",
+		})
+		if er != nil {
+			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
+		}
 	}
-	claims, err := a.redeemCallback(state.(string), r.URL, r.Context())
-	if err != nil {
-		a.log.WithError(err).Warning("failed to redeem code")
-		rw.WriteHeader(400)
-		// To prevent the user from just refreshing and cause more errors, delete
-		// the state from the session
-		delete(s.Values, constants.SessionOAuthState)
-		err := s.Save(r, rw)
+
+	redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
+
+	if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
+		dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")
+		// In forward_domain we only check that the current URL's host
+		// ends with the cookie domain (remove the leading period if set)
+		if !strings.HasSuffix(r.URL.Hostname(), dom) {
+			a.log.WithField("url", r.URL.String()).WithField("cd", dom).Warning("Invalid redirect found")
+			redirectUrl = a.proxyConfig.ExternalHost
+		}
+	}
+	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
+		s.Values[constants.SessionRedirect] = redirectUrl
+		err = s.Save(r, rw)
 		if err != nil {
-			a.log.WithError(err).Warning("failed to save session")
-			rw.WriteHeader(400)
-			return
+			a.log.WithError(err).Warning("failed to save session before redirect")
 		}
-		return
 	}
-	s.Options.MaxAge = int(time.Until(time.Unix(int64(claims.Exp), 0)).Seconds())
-	s.Values[constants.SessionClaims] = &claims
-	err = s.Save(r, rw)
-	if err != nil {
-		a.log.WithError(err).Warning("failed to save session")
-		rw.WriteHeader(400)
-		return
+
+	urlArgs := url.Values{
+		redirectParam: []string{redirectUrl},
 	}
-	a.redirect(rw, r)
+	authUrl := urlJoin(a.proxyConfig.ExternalHost, "/outpost.goauthentik.io/start")
+	http.Redirect(rw, r, authUrl+"?"+urlArgs.Encode(), http.StatusFound)
 }
diff --git a/internal/outpost/proxyv2/application/oauth_callback.go b/internal/outpost/proxyv2/application/oauth_callback.go
index 13f28e67a335..ac6462637816 100644
--- a/internal/outpost/proxyv2/application/oauth_callback.go
+++ b/internal/outpost/proxyv2/application/oauth_callback.go
@@ -3,22 +3,43 @@ package application
 import (
 	"context"
 	"fmt"
+	"net/http"
 	"net/url"
+	"time"
 
-	log "github.com/sirupsen/logrus"
+	"goauthentik.io/internal/outpost/proxyv2/constants"
 	"golang.org/x/oauth2"
 )
 
-func (a *Application) redeemCallback(savedState string, u *url.URL, c context.Context) (*Claims, error) {
-	state := u.Query().Get("state")
-	a.log.WithFields(log.Fields{
-		"states":   savedState,
-		"expected": state,
-	}).Trace("tracing states")
-	if savedState != state {
-		return nil, fmt.Errorf("invalid state")
+func (a *Application) handleAuthCallback(rw http.ResponseWriter, r *http.Request) {
+	state := a.stateFromRequest(r)
+	if state == nil {
+		a.log.Warning("invalid state")
+		a.redirect(rw, r)
+		return
 	}
+	claims, err := a.redeemCallback(r.URL, r.Context())
+	if err != nil {
+		a.log.WithError(err).Warning("failed to redeem code")
+		a.redirect(rw, r)
+		return
+	}
+	s, err := a.sessions.Get(r, a.SessionName())
+	if err != nil {
+		a.log.WithError(err).Trace("failed to get session")
+	}
+	s.Options.MaxAge = int(time.Until(time.Unix(int64(claims.Exp), 0)).Seconds())
+	s.Values[constants.SessionClaims] = &claims
+	err = s.Save(r, rw)
+	if err != nil {
+		a.log.WithError(err).Warning("failed to save session")
+		rw.WriteHeader(400)
+		return
+	}
+	a.redirect(rw, r)
+}
 
+func (a *Application) redeemCallback(u *url.URL, c context.Context) (*Claims, error) {
 	code := u.Query().Get("code")
 	if code == "" {
 		return nil, fmt.Errorf("blank code")
diff --git a/internal/outpost/proxyv2/application/oauth_state.go b/internal/outpost/proxyv2/application/oauth_state.go
new file mode 100644
index 000000000000..798e658396f1
--- /dev/null
+++ b/internal/outpost/proxyv2/application/oauth_state.go
@@ -0,0 +1,95 @@
+package application
+
+import (
+	"encoding/base32"
+	"encoding/base64"
+	"fmt"
+	"net/http"
+
+	"github.com/golang-jwt/jwt/v5"
+	"github.com/gorilla/securecookie"
+	"github.com/mitchellh/mapstructure"
+)
+
+type OAuthState struct {
+	Issuer    string `json:"iss" mapstructure:"iss"`
+	SessionID string `json:"sid" mapstructure:"sid"`
+	State     string `json:"state" mapstructure:"state"`
+	Redirect  string `json:"redirect" mapstructure:"redirect"`
+}
+
+func (oas *OAuthState) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil }
+func (oas *OAuthState) GetIssuedAt() (*jwt.NumericDate, error)       { return nil, nil }
+func (oas *OAuthState) GetNotBefore() (*jwt.NumericDate, error)      { return nil, nil }
+func (oas *OAuthState) GetIssuer() (string, error)                   { return oas.Issuer, nil }
+func (oas *OAuthState) GetSubject() (string, error)                  { return oas.State, nil }
+func (oas *OAuthState) GetAudience() (jwt.ClaimStrings, error)       { return nil, nil }
+
+var base32RawStdEncoding = base32.StdEncoding.WithPadding(base32.NoPadding)
+
+func (a *Application) createState(r *http.Request, fwd string) (string, error) {
+	s, _ := a.sessions.Get(r, a.SessionName())
+	if s.ID == "" {
+		// Ensure session has an ID
+		s.ID = base32RawStdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
+	}
+	st := &OAuthState{
+		Issuer:    fmt.Sprintf("goauthentik.io/outpost/%s", a.proxyConfig.GetClientId()),
+		State:     base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)),
+		SessionID: s.ID,
+		Redirect:  fwd,
+	}
+	if fwd == "" {
+		// This should only really be hit for nginx forward_auth
+		// as for that the auth start redirect URL is generated by the
+		// reverse proxy, and as such we won't have a request we just
+		// denied to reference for final URL
+		rd, ok := a.checkRedirectParam(r)
+		if ok {
+			a.log.WithField("rd", rd).Trace("Setting redirect")
+			st.Redirect = rd
+		}
+	}
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, st)
+	tokenString, err := token.SignedString([]byte(a.proxyConfig.GetCookieSecret()))
+	if err != nil {
+		return "", err
+	}
+	return tokenString, nil
+}
+
+func (a *Application) stateFromRequest(r *http.Request) *OAuthState {
+	stateJwt := r.URL.Query().Get("state")
+	token, err := jwt.Parse(stateJwt, func(token *jwt.Token) (interface{}, error) {
+		// Don't forget to validate the alg is what you expect:
+		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+		}
+		return []byte(a.proxyConfig.GetCookieSecret()), nil
+	})
+	if err != nil {
+		a.log.WithError(err).Warning("failed to parse state jwt")
+		return nil
+	}
+	iss, err := token.Claims.GetIssuer()
+	if err != nil {
+		a.log.WithError(err).Warning("state jwt without issuer")
+		return nil
+	}
+	if iss != fmt.Sprintf("goauthentik.io/outpost/%s", a.proxyConfig.GetClientId()) {
+		a.log.WithField("issuer", iss).Warning("invalid state jwt issuer")
+		return nil
+	}
+	claims := &OAuthState{}
+	err = mapstructure.Decode(token.Claims, &claims)
+	if err != nil {
+		a.log.WithError(err).Warning("failed to mapdecode")
+		return nil
+	}
+	s, _ := a.sessions.Get(r, a.SessionName())
+	if claims.SessionID != s.ID {
+		a.log.WithField("is", claims.SessionID).WithField("should", s.ID).Warning("mismatched session ID")
+		return nil
+	}
+	return claims
+}
diff --git a/internal/outpost/proxyv2/application/test.go b/internal/outpost/proxyv2/application/test.go
index 827bf797035a..f7970776c415 100644
--- a/internal/outpost/proxyv2/application/test.go
+++ b/internal/outpost/proxyv2/application/test.go
@@ -2,6 +2,9 @@ package application
 
 import (
 	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"testing"
 
 	"goauthentik.io/api/v3"
 	"goauthentik.io/internal/outpost/ak"
@@ -45,11 +48,11 @@ func newTestApplication() *Application {
 			Name:                       ak.TestSecret(),
 			ClientId:                   api.PtrString(ak.TestSecret()),
 			ClientSecret:               api.PtrString(ak.TestSecret()),
+			CookieDomain:               api.PtrString(""),
 			CookieSecret:               api.PtrString(ak.TestSecret()),
 			ExternalHost:               "https://ext.t.goauthentik.io",
 			InternalHost:               api.PtrString("http://backend"),
 			InternalHostSslValidation:  api.PtrBool(true),
-			CookieDomain:               api.PtrString(""),
 			Mode:                       api.PROXYMODE_FORWARD_SINGLE.Ptr(),
 			SkipPathRegex:              api.PtrString("/skip.*"),
 			BasicAuthEnabled:           api.PtrBool(true),
@@ -67,3 +70,25 @@ func newTestApplication() *Application {
 	ts.apps = append(ts.apps, a)
 	return a
 }
+
+func (a *Application) assertState(t *testing.T, req *http.Request, response *httptest.ResponseRecorder) (*url.URL, *OAuthState) {
+	loc, _ := response.Result().Location()
+	q := loc.Query()
+	state := q.Get("state")
+	a.log.WithField("actual", state).Warning("actual state")
+	// modify request to set state so we can parse it
+	nr := req.Clone(req.Context())
+	nrq := nr.URL.Query()
+	nrq.Set("state", state)
+	nr.URL.RawQuery = nrq.Encode()
+	// parse state
+	parsed := a.stateFromRequest(nr)
+	if parsed == nil {
+		panic("Could not parse state")
+	}
+
+	// Remove state from URL
+	q.Del("state")
+	loc.RawQuery = q.Encode()
+	return loc, parsed
+}
diff --git a/internal/outpost/proxyv2/application/utils.go b/internal/outpost/proxyv2/application/utils.go
index f8a2d6575f92..248d7ddf0920 100644
--- a/internal/outpost/proxyv2/application/utils.go
+++ b/internal/outpost/proxyv2/application/utils.go
@@ -4,10 +4,6 @@ import (
 	"net/http"
 	"net/url"
 	"strconv"
-	"strings"
-
-	"goauthentik.io/api/v3"
-	"goauthentik.io/internal/outpost/proxyv2/constants"
 )
 
 func urlJoin(originalUrl string, newPath string) string {
@@ -18,62 +14,18 @@ func urlJoin(originalUrl string, newPath string) string {
 	return u
 }
 
-func (a *Application) redirectToStart(rw http.ResponseWriter, r *http.Request) {
-	s, err := a.sessions.Get(r, a.SessionName())
-	if err != nil {
-		a.log.WithError(err).Warning("failed to decode session")
-	}
-	if r.Header.Get(constants.HeaderAuthorization) != "" && *a.proxyConfig.InterceptHeaderAuth {
-		rw.WriteHeader(401)
-		er := a.errorTemplates.Execute(rw, ErrorPageData{
-			Title:       "Unauthenticated",
-			Message:     "Due to 'Receive header authentication' being set, no redirect is performed.",
-			ProxyPrefix: "/outpost.goauthentik.io",
-		})
-		if er != nil {
-			http.Error(rw, "Internal Server Error", http.StatusInternalServerError)
-		}
-	}
-
-	redirectUrl := urlJoin(a.proxyConfig.ExternalHost, r.URL.Path)
-
-	if a.Mode() == api.PROXYMODE_FORWARD_DOMAIN {
-		dom := strings.TrimPrefix(*a.proxyConfig.CookieDomain, ".")
-		// In forward_domain we only check that the current URL's host
-		// ends with the cookie domain (remove the leading period if set)
-		if !strings.HasSuffix(r.URL.Hostname(), dom) {
-			a.log.WithField("url", r.URL.String()).WithField("cd", dom).Warning("Invalid redirect found")
-			redirectUrl = a.proxyConfig.ExternalHost
-		}
-	}
-	if _, redirectSet := s.Values[constants.SessionRedirect]; !redirectSet {
-		s.Values[constants.SessionRedirect] = redirectUrl
-		err = s.Save(r, rw)
-		if err != nil {
-			a.log.WithError(err).Warning("failed to save session before redirect")
-		}
-	}
-
-	urlArgs := url.Values{
-		redirectParam: []string{redirectUrl},
-	}
-	authUrl := urlJoin(a.proxyConfig.ExternalHost, "/outpost.goauthentik.io/start")
-	http.Redirect(rw, r, authUrl+"?"+urlArgs.Encode(), http.StatusFound)
-}
-
 func (a *Application) redirect(rw http.ResponseWriter, r *http.Request) {
-	redirect := a.proxyConfig.ExternalHost
-	s, _ := a.sessions.Get(r, a.SessionName())
-	redirectR, ok := s.Values[constants.SessionRedirect]
-	if ok {
-		redirect = redirectR.(string)
+	fallbackRedirect := a.proxyConfig.ExternalHost
+	state := a.stateFromRequest(r)
+	if state == nil {
+		rw.WriteHeader(http.StatusBadRequest)
+		return
 	}
-	rd, ok := a.checkRedirectParam(r)
-	if ok {
-		redirect = rd
+	if state.Redirect == "" {
+		state.Redirect = fallbackRedirect
 	}
-	a.log.WithField("redirect", redirect).Trace("final redirect")
-	http.Redirect(rw, r, redirect, http.StatusFound)
+	a.log.WithField("redirect", state.Redirect).Trace("final redirect")
+	http.Redirect(rw, r, state.Redirect, http.StatusFound)
 }
 
 // toString Generic to string function, currently supports actual strings and integers
diff --git a/internal/outpost/proxyv2/hs256/hs256.go b/internal/outpost/proxyv2/hs256/hs256.go
index 3df8c73e82d2..29ace0995d7b 100644
--- a/internal/outpost/proxyv2/hs256/hs256.go
+++ b/internal/outpost/proxyv2/hs256/hs256.go
@@ -3,9 +3,10 @@ package hs256
 import (
 	"context"
 	"encoding/base64"
+	"fmt"
 	"strings"
 
-	"github.com/golang-jwt/jwt"
+	"github.com/golang-jwt/jwt/v5"
 )
 
 type KeySet struct {
@@ -15,17 +16,23 @@ type KeySet struct {
 
 func NewKeySet(secret string) *KeySet {
 	return &KeySet{
-		m:      jwt.GetSigningMethod("HS256"),
+		m:      jwt.SigningMethodHS256,
 		secret: secret,
 	}
 }
 
-func (ks *KeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
-	parts := strings.Split(jwt, ".")
-	err := ks.m.Verify(strings.Join(parts[0:2], "."), parts[2], []byte(ks.secret))
+func (ks *KeySet) VerifySignature(ctx context.Context, rawJWT string) ([]byte, error) {
+	_, err := jwt.Parse(rawJWT, func(token *jwt.Token) (interface{}, error) {
+		// Don't forget to validate the alg is what you expect:
+		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
+		}
+		return []byte(ks.secret), nil
+	})
 	if err != nil {
 		return nil, err
 	}
+	parts := strings.Split(rawJWT, ".")
 	payload, err := base64.RawURLEncoding.DecodeString(parts[1])
 	return payload, err
 }
diff --git a/schema.yml b/schema.yml
index 50a6c85ca15e..bfd194314152 100644
--- a/schema.yml
+++ b/schema.yml
@@ -44146,6 +44146,12 @@ components:
           type: array
           items:
             $ref: '#/components/schemas/LogEvent'
+        expires:
+          type: string
+          format: date-time
+          nullable: true
+        expiring:
+          type: boolean
       required:
       - description
       - duration
diff --git a/web/src/admin/system-tasks/SystemTaskListPage.ts b/web/src/admin/system-tasks/SystemTaskListPage.ts
index e05a59c24936..9947bc0d7c04 100644
--- a/web/src/admin/system-tasks/SystemTaskListPage.ts
+++ b/web/src/admin/system-tasks/SystemTaskListPage.ts
@@ -90,6 +90,27 @@ export class SystemTaskListPage extends TablePage<SystemTask> {
                                 </div>
                             </dd>
                         </div>
+                        <div class="pf-c-description-list__group">
+                            <dt class="pf-c-description-list__term">
+                                <span class="pf-c-description-list__text">${msg("Expiry")}</span>
+                            </dt>
+                            <dd class="pf-c-description-list__description">
+                                <div class="pf-c-description-list__text">
+                                    ${item.expiring
+                                        ? html`
+                                              <pf-tooltip
+                                                  position="top"
+                                                  content=${(
+                                                      item.expires || new Date()
+                                                  ).toLocaleString()}
+                                              >
+                                                  ${getRelativeTime(item.expires || new Date())}
+                                              </pf-tooltip>
+                                          `
+                                        : msg("-")}
+                                </div>
+                            </dd>
+                        </div>
                         <div class="pf-c-description-list__group">
                             <dt class="pf-c-description-list__term">
                                 <span class="pf-c-description-list__text">${msg("Messages")}</span>

Proxy provider incorrect redirect behaviour
**Describe the bug**
This bug manifests as a seemingly random-ish redirect after a page refresh when the proxy token has expired and the user is redirected back to the app from the proxy outpost that has just generated a new token.

To make it more clear - let's say we have a forward auth single application set up correctly and working - let's call the application The Echo Server: `https://echo.domain.tld`. We visit the Echo Server, idle for some time, and manage to wait enough so that the proxy token expires. When the Echo Servers sends a request to some kind of Echo Server's **resource (`https://echo.domain.tld/static/resource.json`)** AFTER the token expires (which fails because of the expired token) and THEN we refresh the current page (`https://echo.domain.tld/home`), we get redirected to authentik proxy outpost which in turns generates a new token and redirects the user back to the Echo Server - but when authentik eventually redirects the user back to the app the URL is **`https://echo.domain.tld/static/resource.json`** and not the original path from which the flow started `https://echo.domain.tld/home`.


**To Reproduce**
Steps to reproduce the behavior:

1. Have a working setup of a protected application with a proxy provider in Forward auth (single application) mode ("The Echo Server" described above)
2. In the app's proxy provider update the Token validity setting to a short duration, e.g. `seconds=30` for demonstration purposes
3. Go to the app (e.g. `https://echo.domain.tld/home`) and successfully authenticate/authorize.
4. Wait 30 seconds until the token expires
5. Open a developer console and simulate an artificial resource request of some kind: `fetch("https://echo.domain.tld/static/resource.json")` - the fetch fails because the token has expired. You can verify in the network tab that the fetch request gets redirected to the outpost and fails: `https://echo.domain.tld/outpost.goauthentik.io/start?rd=https%3A%2F%2Fecho.domain.tld%2Fstatic%2Fresource.json`.
6. Now refresh the page while still being at the same URL: `https://echo.domain.tld/home` - You can verify in the network tab that the refresh request gets redirected to the outpost _with correct redirect argument set_: `https://echo.domain.tld/outpost.goauthentik.io/start?rd=https%3A%2F%2Fecho.domain.tld%2Fhome`
7. You eventually get redirected back to the app's resource requested in step 5: `https://echo.domain.tld/static/resource.json`

**Expected behavior**
I would expect to be eventually redirected back to the `https://echo.domain.tld/home` page.

**Logs**

<details>
<summary>Logs</summary>

```
{"event":"/outpost.goauthentik.io/auth/nginx","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"0.343","scheme":"http","size":0,"status":200,"timestamp":"2023-05-14T22:28:10Z","user":"UserName","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/auth/nginx","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"0.930","scheme":"http","size":21,"status":401,"timestamp":"2023-05-14T22:29:07Z","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/start?rd=https%3A%2F%2Fecho.domain.tld%2Fstatic%2Fresource.json","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"1.123","scheme":"http","size":359,"status":302,"timestamp":"2023-05-14T22:29:07Z","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/auth/nginx","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"0.933","scheme":"http","size":21,"status":401,"timestamp":"2023-05-14T22:29:11Z","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/start?rd=https%3A%2F%2Fecho.domain.tld%2Fhome","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"1.319","scheme":"http","size":359,"status":302,"timestamp":"2023-05-14T22:29:11Z","user_agent":"USER_AGENT"}
{"auth_via": "session", "event": "/application/o/authorize/?client_id=ffffffffffffffffffffffffffffffff&redirect_uri=https%3A%2F%2Fecho.domain.tld%2Foutpost.goauthentik.io%2Fcallback%3FX-authentik-auth-callback%3Dtrue&response_type=code&scope=openid+profile+email+ak_proxy&state=NdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo", "host": "auth.domain.tld", "level": "info", "logger": "authentik.asgi", "method": "GET", "pid": 24, "remote": "xxx.xxx.xxx.xxx", "request_id": "5b1ce3f63ab44b67ae482cd4eef3548d", "runtime": 74, "scheme": "https", "status": 302, "timestamp": "2023-05-14T22:29:11.311489", "user": "UserName", "user_agent": "USER_AGENT"}
{"auth_via": "session", "event": "/if/flow/default-provider-authorization-explicit-consent/?client_id=ffffffffffffffffffffffffffffffff&redirect_uri=https%3A%2F%2Fecho.domain.tld%2Foutpost.goauthentik.io%2Fcallback%3FX-authentik-auth-callback%3Dtrue&response_type=code&scope=openid+profile+email+ak_proxy&state=NdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo", "host": "auth.domain.tld", "level": "info", "logger": "authentik.asgi", "method": "GET", "pid": 24, "remote": "xxx.xxx.xxx.xxx", "request_id": "9dabf88c7f7f40cb909a317c47132181", "runtime": 33, "scheme": "https", "status": 200, "timestamp": "2023-05-14T22:29:11.362915", "user": "UserName", "user_agent": "USER_AGENT"}
{"event":"/static/dist/poly.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"4.872","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:11Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/standalone/loading/vendor-320681c9.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"1.094","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/standalone/loading/api-f65fd993.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.742","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/standalone/loading/locale-en-f660cb3b.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.523","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/standalone/loading/index.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.438","scheme":"http","size":53898,"status":200,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/standalone/loading/index.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.196","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/FlowInterface-d33d9ac4.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"2.285","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/FlowInterface.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.127","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/locale-en-f660cb3b.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"1.856","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/vendor-cm-00a4719e.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"7.299","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/api-befd9628.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"13.889","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/assets/icons/icon_left_brand.svg","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.511","scheme":"http","size":4861,"status":200,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/if/flow/default-provider-authorization-explicit-consent/assets/fonts/RedHatText/RedHatText-Regular.woff2","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.523","scheme":"http","size":3768,"status":200,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/if/flow/default-provider-authorization-explicit-consent/assets/fonts/RedHatDisplay/RedHatDisplay-Medium.woff2","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"0.521","scheme":"http","size":28661,"status":200,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/vendor-25865c6e.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"45.016","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"event":"/static/dist/flow/FlowInterface-d33d9ac4.js.map","host":"auth.domain.tld","level":"info","logger":"authentik.router","method":"GET","remote":"xxx.xxx.xxx.xxx","runtime":"1.530","scheme":"http","size":0,"status":304,"timestamp":"2023-05-14T22:29:12Z","user_agent":"USER_AGENT"}
{"auth_via": "session", "event": "/api/v3/flows/executor/default-provider-authorization-explicit-consent/?query=client_id%3Dffffffffffffffffffffffffffffffff%26redirect_uri%3Dhttps%253A%252F%252Fecho.domain.tld%252Foutpost.goauthentik.io%252Fcallback%253FX-authentik-auth-callback%253Dtrue%26response_type%3Dcode%26scope%3Dopenid%2Bprofile%2Bemail%2Bak_proxy%26state%3DNdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo", "host": "auth.domain.tld", "level": "info", "logger": "authentik.asgi", "method": "GET", "pid": 24, "remote": "xxx.xxx.xxx.xxx", "request_id": "bccf832ab85840c7899bef18fb76899e", "runtime": 233, "scheme": "https", "status": 302, "timestamp": "2023-05-14T22:29:12.466727", "user": "UserName", "user_agent": "USER_AGENT"}
{"action": "authorize_application", "auth_via": "session", "client_ip": "xxx.xxx.xxx.xxx", "context": {"authorized_application": {"app": "authentik_core", "model_name": "application", "name": "Echo server", "pk": "d208963c731d4cb282ae64397f731688"}, "flow": "a8c59e9e6fbc4e1d9a53365db1bf8704", "http_request": {"args": {"client_id": "ffffffffffffffffffffffffffffffff", "redirect_uri": "https://echo.domain.tld/outpost.goauthentik.io/callback?X-authentik-auth-callback=true", "response_type": "code", "scope": "openid profile email ak_proxy", "state": "NdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo"}, "method": "GET", "path": "/api/v3/flows/executor/default-provider-authorization-explicit-consent/"}, "scopes": "openid profile email ak_proxy"}, "event": "Created Event", "host": "auth.domain.tld", "level": "info", "logger": "authentik.events.models", "pid": 24, "request_id": "15975e5a84894e668b1127b804d7b3d8", "timestamp": "2023-05-14T22:29:12.654030", "user": {"email": "email@example.com", "pk": 12, "username": "UserName"}}
{"auth_via": "session", "event": "Task published", "host": "auth.domain.tld", "level": "info", "logger": "authentik.root.celery", "pid": 24, "request_id": "15975e5a84894e668b1127b804d7b3d8", "task_id": "94594c44-1672-4710-b659-96c22b3580f6", "task_name": "authentik.events.tasks.event_notification_handler", "timestamp": "2023-05-14T22:29:12.678197"}
{"auth_via": "session", "event": "/api/v3/flows/executor/default-provider-authorization-explicit-consent/?query=client_id%3Dffffffffffffffffffffffffffffffff%26redirect_uri%3Dhttps%253A%252F%252Fecho.domain.tld%252Foutpost.goauthentik.io%252Fcallback%253FX-authentik-auth-callback%253Dtrue%26response_type%3Dcode%26scope%3Dopenid%2Bprofile%2Bemail%2Bak_proxy%26state%3DNdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo", "host": "auth.domain.tld", "level": "info", "logger": "authentik.asgi", "method": "GET", "pid": 24, "remote": "xxx.xxx.xxx.xxx", "request_id": "15975e5a84894e668b1127b804d7b3d8", "runtime": 113, "scheme": "https", "status": 200, "timestamp": "2023-05-14T22:29:12.709587", "user": "UserName", "user_agent": "USER_AGENT"}
{"auth_via": "unauthenticated", "event": "/-/health/ready/", "host": "localhost:9000", "level": "info", "logger": "authentik.asgi", "method": "HEAD", "pid": 24, "remote": "127.0.0.1", "request_id": "5cc814939c734f85ab612559d77ee914", "runtime": 18, "scheme": "http", "status": 204, "timestamp": "2023-05-14T22:29:12.845074", "user": "", "user_agent": "goauthentik.io lifecycle Healthcheck"}
{"auth_via": "unauthenticated", "event": "/application/o/token/", "host": "auth.domain.tld", "level": "info", "logger": "authentik.asgi", "method": "POST", "pid": 10514, "remote": "127.0.0.1", "request_id": "dbc6b792cbc247dd8a879fb0dd8ec8f4", "runtime": 54, "scheme": "https", "status": 200, "timestamp": "2023-05-14T22:29:13.024719", "user": "", "user_agent": "goauthentik.io/outpost/2023.4.1 (provider=Echo server proxy)"}
{"event":"/outpost.goauthentik.io/callback?X-authentik-auth-callback=true&code=942d95ae2232466aa67a89e8bc8f826f&state=NdC1Ol7jMCT8oS_P9oKDQ3J6gvnETS4eWUbMbo4DbRo","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"57.903","scheme":"http","size":68,"status":302,"timestamp":"2023-05-14T22:29:13Z","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/auth/nginx","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"0.308","scheme":"http","size":0,"status":200,"timestamp":"2023-05-14T22:29:13Z","user":"UserName","user_agent":"USER_AGENT"}
{"event":"/outpost.goauthentik.io/auth/nginx","host":"echo.domain.tld","level":"info","logger":"authentik.outpost.proxyv2.application","method":"GET","name":"Echo server proxy","remote":"xxx.xxx.xxx.xxx","runtime":"0.486","scheme":"http","size":0,"status":200,"timestamp":"2023-05-14T22:29:13Z","user":"UserName","user_agent":"USER_AGENT"}
```

</details>

**Version and Deployment (please complete the following information):**

-   authentik version: 2023.4.1
-   Deployment: docker-compose

**Additional context**
Using nginx reverse proxy.

There are also users on Discord experiencing the same behaviour: https://discord.com/channels/809154715984199690/809154716507963434/1101389383300567060
