Programação Por Equipe DevBras

Capa do artigo: Perfil OIDC undefined: por que o nome não veio no token e como tratar

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 lia name.
  • 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.

  1. Liste o que você realmente precisa. No painel: nome para o cabeçalho e e-mail para o rodapé. Isso significa openid profile email.
  2. Configure o cliente pedindo esses escopos e carregando o userinfo.
  3. Veja o que voltou, sem logar o token. Imprima apenas os nomes dos claims e os escopos concedidos.
  4. Compare com o provedor. Se um escopo pedido não voltou, o cadastro do cliente no provedor não o libera.
  5. Trate ausência no código com uma regra de exibição explícita.
  6. 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.name sem 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ó openid e 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 scope do cliente lista só o que a aplicação usa, incluindo openid.
  • O cadastro do cliente no provedor libera todos os escopos pedidos.
  • Você sabe se o perfil precisa do userinfo e configurou loadUserInfo de 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 iss e sub.
  • 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?

Avalie este artigo

Referência: https://github.com/patrickmonteiro/quasarOidcClient/issues/2