Perfil OIDC undefined: por que o nome não veio no token e como tratar
O login passa e a tela quebra ao ler profile.name. Entenda escopo, claims, userinfo e configuração do provedor, e trate campo ausente sem gravar undefined no banco.
Segunda, 9h, o painel em Quasar ganhou login por OIDC. Em homologação tudo certo. Em produção, metade dos usuários vê uma tela branca e o console mostra Cannot read properties of undefined (reading 'name'). O login passou. O provedor autenticou. Só que o claim que o exemplo do tutorial usava não estava no token desses usuários.
A resposta curta: o perfil do OIDC contém apenas os claims que o provedor decidiu devolver, e isso depende do escopo que você pediu, do que o cliente tem permissão para receber, de onde o claim mora (ID token ou endpoint userinfo) e de o usuário ter aquele dado preenchido. Peça os escopos certos, confira o que voltou de verdade e trate todo claim além de sub como opcional.
De onde vem o objeto profile
Bibliotecas como a oidc-client-ts (sucessora da antiga oidc-client) montam user.profile a partir dos claims do ID token. Se você ligar loadUserInfo, a biblioteca também chama o endpoint userinfo do provedor e junta o resultado ao perfil. Ou seja, o objeto que você lê na tela é a soma do que veio em dois lugares, e qualquer um deles pode vir mais magro do que você imagina.
O único claim garantido para identificar a pessoa é o sub, junto com o emissor (iss). Nome, e-mail e foto são dados de perfil, liberados por escopos. A especificação do OpenID Connect define escopos padrão: profile para claims como name, given_name, family_name e preferred_username; email para email e email_verified. Pedir o escopo não obriga o provedor a mandar o claim; apenas autoriza.
As quatro razões para o claim não aparecer
- O escopo não foi pedido. O exemplo usava
scope: 'openid'e lianame. - O cliente não tem aquele escopo liberado no provedor. Em provedores como o Keycloak, cada cliente tem uma lista de escopos permitidos; o pedido de um escopo fora da lista é ignorado ou recusado, conforme a configuração.
- O claim só existe no userinfo. Alguns provedores deixam o ID token enxuto e entregam o perfil completo apenas no endpoint userinfo. Sem
loadUserInfo, ele nunca chega. - O usuário não tem o dado. Conta criada só com login e senha, sem nome cadastrado. O claim simplesmente não existe para essa pessoa.
Há ainda provedores em que certos claims dependem de configuração específica do aplicativo, como claims opcionais. Se o seu provedor é corporativo, a documentação dele sobre claims do ID token é leitura obrigatória antes de escrever a tela.
Como diagnosticar e corrigir, passo a passo
O exemplo é o painel interno da Distribuidora Ponto Sul, em Quasar, autenticando num Keycloak.
- Liste o que você realmente precisa. No painel: nome para o cabeçalho e e-mail para o rodapé. Isso significa
openid profile email. - Configure o cliente pedindo esses escopos e carregando o userinfo.
- Veja o que voltou, sem logar o token. Imprima apenas os nomes dos claims e os escopos concedidos.
- Compare com o provedor. Se um escopo pedido não voltou, o cadastro do cliente no provedor não o libera.
- Trate ausência no código com uma regra de exibição explícita.
- No backend, leia os claims do token validado, nunca do que o front mandar.
Configuração do cliente
// src/boot/oidc.js
import { UserManager } from 'oidc-client-ts';
export const userManager = new UserManager({
authority: 'https://sso.pontosul.example/realms/interno',
client_id: 'painel-spa',
redirect_uri: window.location.origin + '/callback',
post_logout_redirect_uri: window.location.origin + '/',
response_type: 'code',
scope: 'openid profile email',
loadUserInfo: true
});
Diagnóstico sem vazar nada
// src/pages/CallbackPage.vue (script)
import { userManager } from 'src/boot/oidc';
export default {
async mounted() {
try {
const user = await userManager.signinRedirectCallback();
if (import.meta.env.DEV) {
console.info('escopos concedidos:', user.scope);
console.info('claims presentes:', Object.keys(user.profile).sort());
}
this.$router.replace('/');
} catch (err) {
console.error('falha no retorno do login:', err.message);
this.$router.replace('/erro-login');
}
}
};
Na Ponto Sul, a saída em desenvolvimento foi esta para um usuário antigo:
escopos concedidos: openid email
claims presentes: ["aud", "email", "email_verified", "exp", "iat", "iss", "sub"]
O escopo profile foi pedido e não voltou. O problema não estava no Vue: o cliente painel-spa no Keycloak não tinha profile na lista de escopos. Depois de liberar, um segundo usuário ainda vinha sem name. Esse tinha o cadastro sem nome e sobrenome. As duas causas exigem correções diferentes, e só a listagem dos claims mostrou isso.
Note o que o diagnóstico não imprime: nem o ID token, nem o access token, nem os valores dos claims. Nomes de claim bastam para entender o problema, e o log não vira vazamento. O post log útil não carrega senha nem token trata disso em detalhe.
Regra de exibição explícita
// src/auth/perfil.js
export function nomeParaExibir(profile) {
if (!profile) return 'Usuário';
const completo = [profile.given_name, profile.family_name].filter(Boolean).join(' ');
return profile.name
|| completo
|| profile.preferred_username
|| profile.email
|| 'Usuário';
}
export function emailOuNulo(profile) {
return typeof profile?.email === 'string' && profile.email.length > 0
? profile.email
: null;
}
A cadeia de alternativas é para a tela, não para o banco. Mostrar o preferred_username no cabeçalho quando falta o nome é aceitável. Gravar esse valor na coluna nome como se fosse o nome da pessoa não é, e gravar a string "undefined" é pior ainda: ela passa em validação de campo obrigatório e aparece no relatório do mês seguinte.
O que o backend deve fazer com os claims
A tela é só uma consumidora. A API que recebe o access token deve validar a assinatura, o emissor, a audiência e a expiração, e então ler os claims do token validado. O usuário é identificado pelo par iss e sub, nunca pelo e-mail, que muda e pode faltar. Campos de perfil ausentes viram NULL no banco, e a atualização não deve apagar um nome bom só porque, naquele login, o claim não veio.
Também vale lembrar que o ID token diz quem entrou, não o que a pessoa pode fazer. Se a tela esconde menus com base em claims, a API precisa repetir a checagem; o post ID token não é autorização desenvolve esse ponto. E se o problema for o retorno do login em si, e não o conteúdo do perfil, veja cliente JavaScript no OIDC com o redirect certo.
Erros comuns
- Ler
profile.namesem checar se existe. A tela quebra para quem tem o cadastro incompleto, e esse é exatamente o usuário que você não testou. - Pedir só
openide esperar perfil completo. O provedor autentica e não manda nada além do identificador. - Corrigir no front o que é configuração do provedor. Você adiciona fallback atrás de fallback enquanto o escopo continua bloqueado no cadastro do cliente.
- Logar o token inteiro para depurar. O log passa a conter uma credencial válida, copiada para ferramentas de observabilidade e tickets.
- Gravar o valor do fallback no banco. O nome da pessoa vira o login, o e-mail ou a palavra undefined, e ninguém percebe até o relatório.
- Usar o e-mail como chave do usuário. Quando o e-mail falta ou muda, o sistema cria uma conta nova para a mesma pessoa.
Checklist
- O
scopedo cliente lista só o que a aplicação usa, incluindoopenid. - O cadastro do cliente no provedor libera todos os escopos pedidos.
- Você sabe se o perfil precisa do userinfo e configurou
loadUserInfode acordo. - O diagnóstico imprime nomes de claims e escopos concedidos, nunca tokens.
- Toda leitura de claim de perfil tolera ausência.
- O backend identifica o usuário por
issesub. - Campos de perfil ausentes viram
NULL, nunca texto inventado.
Pegue um usuário de teste sem nome cadastrado e sem e-mail e faça login no seu sistema. A tela abre, e o que fica gravado na tabela de usuários?
Referência: https://github.com/patrickmonteiro/quasarOidcClient/issues/2