Autenticação
Documentação Auth Documentação Identity
Podemos observar que inicialmente temos um guest como provider. Esse provider faz com que todos os usuários compartilhem uma única identidade de guest. Somente é util para fins de testes e iniciar no Backstage, mas não para usar em produção. Por isso uma das primeiras coisas a se configurar é um provider ou mais de um.
Como provedor de autenticação temos os principais do mercado:
- Auth0
- Okta
- Github
- Gitlab
- Bitbucket
- Etc...
Geralmente o código da empresa esta em alguma conta do Github ou Gitlab. O login dentro dessas plataformas fazem SSO com o gerenciador de identidade que a empresa esta usando. Vamos implementar aqui o Github.
Essa configuração estará presente no arquivo app-config.yaml. Abaixo o que temos default no código inicial.
auth:
providers:
guest: {}
Vamos configurar providers, o Github, mas podemos usar vários.
Para isso é necessário na conta do Github criar o provedor de autenticação. Isso é feito em Settings > Developer settings > OAuth Apps.
Como podemos ter diferentes configs para diferentes ambientes, podemos criar mais de um também no Github. Inicialmente vamos criar para o desenvolvimento local.
- Homepage URL: É o url da página inicial, ou seja, de onde isso vem
- Authorization callback URL: É para onde serão enviadas as informações de retorno.
Seguindo a própria documentação para um desenvolvimento local eles nos mostram essas urls.
Criado já teremos o nosso Client ID que precisamos salvar pois será usado e já criar no botão generate a new client secret
para criar a secret.
Exporte essas variaveis de ambiente no seu terminal. Usaremos elas mais tarde para desenvolvimento da imagem.
export AUTH_GITHUB_CLIENT_ID=xxxxxxxxxxxxxxxx
export AUTH_GITHUB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Se quiser podemos criar também no Gitlab. Devemos criar uma aplicação e definir a página de retorno. É necessário definir as seguintes permissões:
- api
- read_user
- read_repository
- write_repository
- openid
- profile
Teremos então o application id que será o nosso AUTH_GITLAB_CLIENT_ID e a secret que será o nosso AUTH_GITLAB_CLIENT_SECRET e precisaremos também exportar.
export AUTH_GITLAB_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AUTH_GITLAB_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
auth:
environment: development
providers:
# guest: {}
github:
development:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
signIn:
resolvers:
- resolver: usernameMatchingUserEntityName
gitlab:
development:
clientId: ${AUTH_GITLAB_CLIENT_ID}
clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
signIn:
resolvers:
- resolver: usernameMatchingUserEntityName
O resolver é o modelo de informação que o provider devolverá baseado no login executado. A lista de resolvers disponíveis é diferente para cada provedor, já que eles frequentemente dependem do modelo de informação retornado do serviço do provedor upstream.
É necessários consultar o resolver em cada um dos providers que deseja implementar para ver o que faz mais sentido.
No caso do Github e do Gitlab o valor é o mesmo que ele trará de volta.
emailMatchingUserEntityProfileEmail
: Corresponde o endereço de e-mail do provedor de autenticação com a entidade User que tem um spec.profile.email.emailLocalPartMatchingUserEntityName
: Corresponde a parte local do endereço de e-mail do provedor de autenticação com a entidade User que tem um name.usernameMatchingUserEntityName
: Corresponde o nome de usuário do provedor de autenticação com a entidade User que tem um name
Usando o usernameMatchingUserEntityName estamos recebendo o username e não o endereço de email.
Vale lembrar que usar o emailLocalPartMatchingUserEntityName trará por exemplo somante a parte antes do @domain.com. Nesse caso é bom definir quais os domínios serão aceitos.
Como não é só configurar, precisa também usar. Vamos adicionar isso package do app backend.
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gitlab-provider
yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-github-provider
yarn install
Agora vamos fazer carregar esses plugins.
Todo plugin do backend deve ser adicionado em packages/backend/src/index.ts
antes da linha backend.start()
.
// auth plugin
backend.add(import('@backstage/plugin-auth-backend'));
// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
// backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
backend.add(import('@backstage/plugin-auth-backend-module-gitlab-provider'));
Seguinte a própria documentação
Agora precisamos no App, no arquivo precisamos fazer alguns ajustes.
// add
import { githubAuthApiRef, gitlabAuthApiRef } from '@backstage/core-plugin-api';
//...
components: {
// comentamos a linha que utiliza o guest, e colocamos os novos.
// SignInPage: props => <SignInPage {...props} auto providers={['guest']} />,
SignInPage: props => (
<SignInPage
{...props}
providers={[
// 'guest',
{
id: 'github-auth-provider',
title: 'GitHub',
message: 'Sign in using GitHub',
apiRef: githubAuthApiRef,
},
{
id: 'gitlab-auth-provider',
title: 'GitLab',
message: 'Sign in using GitLab',
apiRef: gitlabAuthApiRef ,
},
]}
/>
),
},
Reiniciando a aplicando novamente com yarn dev
e fazendo o login utilizando um desses providers, recebemos o seguinte.
Login failed; caused by Error: Failed to sign-in, unable to resolve user identity. Please verify that your catalog contains the expected User entities that would match your configured sign-in resolver.
Se você quiser usar um provedor de autenticação para fazer login de usuários, você precisa configurá-lo explicitamente para ter o login habilitado e também dizer a ele como as identidades externas devem ser mapeadas para identidades de usuários dentro do Backstage.
O que aconteceu é que o resolver devolveu uma informação e este user Identity não existe no nosso catalogo, não criamos usuários dentro do sistema para conferir as informações e fazer o mapeamento. É necessário criar um resolver customizado que não precise fazer esse match por enquanto. No futuro, se você configurar para que catalog que sincronize os usuários do seu SSO provider será bom e poderemos retornar ao resolver que temos agora.
Voltando à situacao anterior, o resolver usado em cada uma das contas foi usernameMatchingUserEntityName
ficando mais claro o motivo do erro.
Backstage User Entity
Uma identidade de usuário dentro do Backstage é construída a partir de duas informações principais:
- Um user identity
- Propriedades de referência
Quando um usuário faz login, um token Backstage é gerado, que é então usado para identificar o usuário dentro do ecossistema Backstage. É encorajado que uma entidade de usuário correspondente também exista dentro do Catálogo de Software, como aconteceu acima, mas não é obrigatório. Se a entidade de usuário existir no catálogo, ela pode ser usada para armazenar dados adicionais sobre o usuário.
As referências de propriedade são usadas para determinar o que o usuário possui. Por exemplo, um usuário Jane ( user:default/jane) pode ter as referências de propriedade user:default/jane, group:default/team-a, e group:default/admins. Dadas essas reivindicações de propriedade, qualquer entidade que seja marcada como propriedade de qualquer um de user:jane, team-a, ou admins seria considerada propriedade de Jane. As referências podem ser usadas para resolver outras relações semelhantes à propriedade, como uma reivindicação para um maintainer.
No Backstage, os resolvers de autenticação mapeiam a identidade de um usuário autenticado através de um provedor externo (como GitHub, Gitlab, etc.) para uma identidade interna do Backstage. Esse mapeamento é crucial porque define quem o usuário é e quais permissões ele terá na plataforma.
Ter múltiplos resolvers pode causar conflitos de identidade, especialmente se o mesmo usuário puder acessar de diferentes provedores, mas for tratado como identidades distintas no Backstage.
Imagine o seguinte cenário: Podemos fazer autenticação tanto do GitHub quanto do GitLab. Um usuário, digamos, David, tem contas em ambos os provedores:
- Conta GitHub: daviddevops
- Conta GitLab: daviddevsecops
Se o Backstage não estiver bem configurado para diferenciar as identidades entre os dois provedores, pode ocorrer o seguinte:
- O Backstage pode interpretar as duas contas (daviddevops e daviddevsecops) como dois usuários completamente diferentes, mesmo que seja a mesma pessoa. Isso cria duplicação de perfis, dificultando a gestão de permissões e atividades.
Observe que, embora seja possível configurar vários provedores de autenticação para serem usados para login, você deve tomar cuidado ao fazer isso. É melhor garantir que os diferentes provedores de autenticação não tenham sobreposição de usuários ou que todos os usuários que conseguem fazer login com vários provedores sempre acabem com a mesma identidade do Backstage. Para a maioria das organizações, faz mais sentido fornecer apenas um método de login. Neste caso, estamos habilitando Github e Gitlab pois o meu usuário em ambas as plataformas é exatamente o mesmo nome, então deve ser mapeado para o mesmo user Identity.
Se a empresa tem um email coorporativo no Github por exemplo, [email protected]
usando o resolver emailLocalPartMatchingUserEntityName
ele deveria fazer match com o fulano.tal eliminando o @empresa.com. Nada impede que [email protected]
possa fazer o login mapeando para o mesmo usuário. Então garanta que somente os domínios específicos sejam utilizados na integração.
auth:
providers:
github:
development:
...
signIn:
resolvers:
- resolver: emailLocalPartMatchingUserEntityName
allowedDomains:
- empresa.com
Token Backstage que encapsula a identidade do usuário é um JWT.
Tenha cuidado ao configurar resolvedores de login, pois eles fazem parte da determinação de quem tem acesso à instância do Backstage e com qual identidade no sistema
. Ter mais de um provider de autenticação aumenta o risco de sequestro de conta.
Criando um Resolver Personalizado
Não temos usuário. Vamos permitir que usuários sejam criados com as contas que fizerem login no Github e Gitlab. Será somente uma demonstração.
Em packages/backend/src/index.ts
vamos adicionar um resolver personalizado, mas antes vamos remover o resolver do app-config.yaml, pois ele tentará usar o que ja foi definido.
auth:
environment: development
providers:
github:
development:
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
# signIn:
# resolvers:
# - resolver: usernameMatchingUserEntityName
gitlab:
development:
clientId: ${AUTH_GITLAB_CLIENT_ID}
clientSecret: ${AUTH_GITLAB_CLIENT_SECRET}
# signIn:
# resolvers:
# - resolver: usernameMatchingUserEntityName
Agora vamos customizar um. Como estou deployando em um ambiente controlado nao vamos fazer nada demais somente aceitar o que vier.
index.ts
import { githubAuthenticator } from '@backstage/plugin-auth-backend-module-github-provider'; //Mantemos os dois
import { gitlabAuthenticator } from '@backstage/plugin-auth-backend-module-gitlab-provider';
import { stringifyEntityRef } from '@backstage/catalog-model';
type AuthProviderId = 'github' | 'gitlab'; // Tipo específico para os providers para reaproveitamenteo de codigo
const customAuthResolver = createBackendModule({
pluginId: 'auth',
moduleId: 'custom-auth-provider',
register(reg) {
reg.registerInit({
deps: { providers: authProvidersExtensionPoint },
async init({ providers }) {
const authProviders: AuthProviderId[] = ['github', 'gitlab']; // Lista tipada
authProviders.forEach((providerId) => {
providers.registerProvider({
providerId,
factory: createOAuthProviderFactory({
authenticator: getAuthenticator(providerId),
async signInResolver(info, ctx) {
const { profile: { email } } = info;
if (!email) {
throw new Error('User profile contained no email');
}
const [userId, domain] = email.split('@');
if (domain !== 'gmail.com') { // Ajuste para o domínio correto
throw new Error(
`Login failed, '${email}' does not belong to the expected domain`,
);
}
// criando a user entity
const userEntity = stringifyEntityRef({
kind: 'User',
name: userId,
namespace: 'default',
});
// colocando o token
return ctx.issueToken({
claims: {
sub: userEntity,
ent: [userEntity],
},
});
},
}),
});
});
},
});
},
});
// Função auxiliar com tipo explícito
function getAuthenticator(providerId: AuthProviderId) {
switch (providerId) {
case 'github':
return githubAuthenticator;
case 'gitlab':
return gitlabAuthenticator; // Certifique-se de ter o autenticador do GitLab definido
default:
throw new Error(`No authenticator available for provider ${providerId}`);
}
}
// Neste ponte vamos remover os que tinhamos antes e usar o novo
backend.add(import('@backstage/plugin-auth-backend'));
// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
// backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
// backend.add(import('@backstage/plugin-auth-backend-module-github-provider'));
// backend.add(import('@backstage/plugin-auth-backend-module-gitlab-provider'));
backend.add(customAuthResolver);
Reinicie sistema com yarn dev e faça o login. Uma coisa importante é que neste caso o seu email do Github e Gitlab precisa estar visível no seu profile.
Como previsto, ambos chegaram no mesmo lugar usam o mesmo email.
Porém não foi mapetado para nenhuma entidade interna.
Logout Automático
Como mecanismo de segurança é interessante habilitar o logout automático que é projetado para desconectar usuários em caso de inatividade.
em packages/app/src/App.tsx podemos adicionar.
import { AutoLogout } from '@backstage/core-components';
// ... App.tsx contents
export default app.createRoot(
<>
// ...
<AutoLogout
idleTimeoutMinutes={30}
useWorkerTimers={false}
logoutIfDisconnected={false}
/>
// ...
</>,
);
Confira melhor sobre isso em auto logout.