diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index 8990771ec281287afde06b9e82804aa0fa103c52..d5c6244df38e965d590a3977e98b727ebbfb4950 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -85,7 +85,7 @@ class Assembly(TaggedItemMixin, ActivityLogMixin, RulesModel):
         verbose_name = _('Assembly')
         verbose_name_plural = _('Assemblies')
         rules_permissions = {
-            'view': is_assembly_member,
+            'view': is_assembly_member | has_perms('core.view_assembly', require_staff=True),
             'add': is_authenticated,
             'change': is_assembly_manager | has_perms('core.change_assembly', require_staff=True),
             'delete': is_assembly_manager | has_perms('core.delete_assembly', require_staff=True),
diff --git a/src/plainui/views/badges.py b/src/plainui/views/badges.py
index dbc254e8b0596f854d073341b9a48adda9ae2fac..83e9bb3ade7ae8cd343c31237e1ac2626e04d095 100644
--- a/src/plainui/views/badges.py
+++ b/src/plainui/views/badges.py
@@ -24,7 +24,7 @@ from django.views.generic.base import TemplateView
 from django.views.generic.detail import DetailView
 from django.views.generic.edit import FormView, UpdateView
 
-from core.models import Assembly, Badge, BadgeToken, UserBadge
+from core.models import Badge, BadgeToken, UserBadge
 from core.models.badges import TokenInvalid
 from core.models.conference import ConferenceMember
 from core.views.list_views import FilteredListView
@@ -108,6 +108,15 @@ class RedeemTokenDetailView(ConferenceRequiredMixin, DetailView):
     model = BadgeToken
     template_name = 'plainui/badges/token_detail.html.j2'
     context_object_name = 'token'
+    require_user = True
+
+    def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
+        if not BadgeToken.type_is(token := self.get_object()):  # pragma: no cover
+            raise ValueError('Token is not a badge token')
+        if self.request.user.has_perm('core.view_assembly', token.badge.issuing_assembly):
+            return super().get(request, *args, **kwargs)
+        else:
+            return HttpResponseRedirect(f"{reverse('plainui:manage_badges')}?redeem_token={token.token}")
 
     def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
         # Regenerate the qr code if needed
@@ -119,12 +128,6 @@ class RedeemTokenDetailView(ConferenceRequiredMixin, DetailView):
             'conf': self.conf,
         }
 
-    def get_queryset(self) -> QuerySet[BadgeToken]:
-        manageable = Assembly.objects.manageable_by_user(user=self.request.user, conference=self.conf, staff_can_manage=False)
-        qs = super().get_queryset()
-        qs = qs.filter(badge__issuing_assembly__in=manageable)
-        return qs
-
 
 @method_decorator(ratelimit(group='redeem_badge', key='user', rate=settings.BADGE_RATE_LIMIT), name='dispatch')
 class RedeemBadgeView(ConferenceRequiredMixin, FormView):