O algoritmo none
A assinatura é o que torna o token confiável. Um servidor que aceita alg none abre mão dela.
A única parte do JWT que o atacante não consegue forjar é a assinatura, porque ela depende de um segredo que só o servidor tem. Mudar o payload (virar role: admin) é fácil; o que impede o truque é o servidor recalcular a assinatura e ver que não bate.
O padrão do JWT prevê um valor de algoritmo chamado none, pensado para casos em que outra camada já garante a origem. Quando o header diz {"alg":"none"}, não há assinatura: a terceira parte fica vazia. Se o servidor lê o alg do próprio token e, vendo none, decide não conferir nada, o atacante monta o token que quiser:
header = {"alg":"none"}
payload = {"role":"admin"}
token = base64url(header) + "." + base64url(payload) + "."
O erro de fundo é deixar o atacante escolher como o token dele vai ser conferido. O conserto é fixar o algoritmo aceito no servidor (algorithms=["HS256"]) e nunca deduzir isso do header. Com o algoritmo fixado, um token none é recusado antes de qualquer outra coisa.