Por que o código do autenticador muda a cada 30 segundos e não a cada login?
Porque o contador do TOTP é o tempo Unix dividido pelo período configurado, 30 segundos por padrão na RFC 6238; a cada vez que esse contador muda de valor inteiro, o HMAC sobre ele muda, e por consequência o código truncado também muda, independente de haver ou não uma tentativa de login em andamento.
O segredo TOTP em base32 é a mesma coisa que o QR code?
Não exatamente: o segredo em base32 (como `JBSWY3DPEHPK3PXP`) é só o dado bruto usado no cálculo HMAC; o QR code codifica esse segredo dentro de uma URI `otpauth://` completa, que também carrega o nome da conta, o emissor, o algoritmo, a quantidade de dígitos e o período, tudo que o app precisa para gerar os mesmos códigos.
O que acontece se o relógio do servidor estiver atrasado?
Como o TOTP não troca mensagens para se sincronizar, ele depende inteiramente do relógio de cada lado bater dentro de uma janela de tolerância que o verificador aceita, geralmente um ou dois períodos de 30 segundos para trás e para frente; um desvio maior que isso faz até o código correto do usuário ser rejeitado, um problema de sincronização de horário, não de segurança do algoritmo.
TOTP é tão seguro quanto uma chave de segurança física FIDO2?
Não no mesmo nível: TOTP é simétrico, o servidor guarda o mesmo segredo usado para gerar o código, então quem rouba esse banco de dados consegue gerar códigos válidos para sempre. Uma chave FIDO2/WebAuthn usa um par de chaves assimétrico, e a chave privada nunca sai do dispositivo físico nem é armazenada pelo servidor, o que elimina esse risco específico.