Tutorial: Adicionar autenticação e permissões ao seu aplicativo ao usar o Azure Web PubSub
Em Criar um aplicativo de chat, você aprendeu como usar APIs WebSocket para enviar e receber dados com o Azure Web PubSub. Você percebe que, para simplificar, ele não requer nenhuma autenticação. Embora o Azure Web PubSub exija um token de acesso para ser conectado, a negotiate
API usada no tutorial para gerar o token de acesso não precisa de autenticação. Qualquer pessoa pode chamar essa API para obter um token de acesso.
Em um aplicativo do mundo real, você normalmente deseja que o usuário entre primeiro, antes que ele possa usar seu aplicativo. Neste tutorial, você aprenderá a integrar o Web PubSub com o sistema de autenticação e autorização do seu aplicativo, para torná-lo mais seguro.
Você pode encontrar o exemplo de código completo deste tutorial no GitHub.
Neste tutorial, irá aprender a:
- Ativar autenticação do GitHub
- Adicionar middleware de autenticação ao seu aplicativo
- Adicionar permissões aos clientes
Importante
As cadeias de conexão brutas aparecem neste artigo apenas para fins de demonstração.
Uma cadeia de conexão inclui as informações de autorização necessárias para seu aplicativo acessar o serviço Azure Web PubSub. A chave de acesso dentro da cadeia de conexão é semelhante a uma senha de root para o seu serviço. Em ambientes de produção, proteja sempre as suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com WebPubSubServiceClient
o .
Evite distribuir chaves de acesso para outros usuários, codificá-las ou salvá-las em qualquer lugar em texto simples acessível a outras pessoas. Rode as chaves se acreditar que podem ter sido comprometidas.
Adicionar autenticação ao aplicativo de sala de chat
Este tutorial reutiliza o aplicativo de bate-papo criado em Criar um aplicativo de bate-papo. Você também pode clonar o exemplo de código completo para o aplicativo de bate-papo do GitHub.
Neste tutorial, você adiciona autenticação ao aplicativo de chat e o integra ao Web PubSub.
Primeiro, adicione a autenticação do GitHub à sala de chat para que o usuário possa usar uma conta do GitHub para entrar.
Instale dependências.
npm install --save cookie-parser npm install --save express-session npm install --save passport npm install --save passport-github2
Localize o
server.js
arquivo em seu diretório e habilite a autenticação do GitHub adicionando o seguinte código aoserver.js
:const app = express(); const users = {}; passport.use( new GitHubStrategy({ clientID: process.argv[3], clientSecret: process.argv[4] }, (accessToken, refreshToken, profile, done) => { users[profile.id] = profile; return done(null, profile); } )); passport.serializeUser((user, done) => { done(null, user.id); }); passport.deserializeUser((id, done) => { if (users[id]) return done(null, users[id]); return done(`invalid user id: ${id}`); }); app.use(cookieParser()); app.use(session({ resave: false, saveUninitialized: true, secret: 'keyboard cat' })); app.use(passport.initialize()); app.use(passport.session()); app.get('/auth/github', passport.authenticate('github', { scope: ['user:email'] })); app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/' }));
O código anterior usa Passport.js para habilitar a autenticação do GitHub. Aqui está uma ilustração simples de como funciona:
/auth/github
redireciona para github.com para entrar.- Depois de entrar, o GitHub redireciona você com
/auth/github/callback
um código para seu aplicativo para concluir a autenticação. (Para ver como o perfil retornado do GitHub é verificado e persistido no servidor, consulte o retorno de chamada verificado empassport.use()
.) - Depois que a autenticação for concluída, você será redirecionado para a página inicial (
/
) do site.
Para obter mais detalhes sobre o GitHub OAuth e Passport.js, consulte os seguintes artigos:
Para testar isso, você precisa primeiro criar um aplicativo GitHub OAuth:
- Vá para https://www.github.com, abra seu perfil e selecione Configurações>Configurações do desenvolvedor.
- Vá para Aplicativos OAuth e selecione Novo Aplicativo OAuth.
- Preencha o nome do aplicativo e o URL da página inicial (o URL pode ser o que você quiser) e defina URL de retorno de chamada de autorização como
http://localhost:8080/auth/github/callback
. Esse URL corresponde à API de retorno de chamada que você expôs no servidor. - Depois que o aplicativo for registrado, copie o ID do cliente e selecione Gerar um novo segredo do cliente.
As cadeias de conexão brutas aparecem neste artigo apenas para fins de demonstração. Em ambientes de produção, proteja sempre as suas chaves de acesso. Use o Azure Key Vault para gerenciar e girar suas chaves com segurança e proteger sua conexão com
WebPubSubServiceClient
o .Execute o comando abaixo para testar as configurações, não se esqueça de substituir
<connection-string>
,<client-id>
e<client-secret>
com seus valores.export WebPubSubConnectionString="<connection-string>" export GitHubClientId="<client-id>" export GitHubClientSecret="<client-secret>" node server
Agora aberto
http://localhost:8080/auth/github
. Você será redirecionado para o GitHub para fazer login. Depois de entrar, você será redirecionado para o aplicativo de bate-papo.Atualize a sala de chat para usar a identidade obtida do GitHub, em vez de solicitar ao usuário um nome de usuário.
Atualize
public/index.html
para ligar/negotiate
diretamente sem passar um ID de usuário.let messages = document.querySelector('#messages'); let res = await fetch(`/negotiate`); if (res.status === 401) { let m = document.createElement('p'); m.innerHTML = 'Not authorized, click <a href="/auth/github">here</a> to login'; messages.append(m); return; } let data = await res.json(); let ws = new WebSocket(data.url);
Quando um utilizador tem sessão iniciada, o pedido transporta automaticamente a identidade do utilizador através de um cookie. Então você só precisa verificar se o
req
usuário existe no objeto e adicionar o nome de usuário ao token de acesso Web PubSub:app.get('/negotiate', async (req, res) => { if (!req.user || !req.user.username) { res.status(401).send('missing user id'); return; } let options = { userId: req.user.username }; let token = await serviceClient.getClientAccessToken(options); res.json({ url: token.url }); });
Agora execute novamente o servidor e você verá uma mensagem "não autorizado" pela primeira vez que abrir a sala de chat. Selecione o link de entrada para entrar e, em seguida, você verá que ele funciona como antes.
Trabalhar com permissões
Nos tutoriais anteriores, você aprendeu a usar WebSocket.send()
para publicar mensagens diretamente para outros clientes usando o subprotocolo. Em um aplicativo real, talvez você não queira que o cliente possa publicar ou assinar qualquer grupo sem controle de permissão. Nesta seção, você verá como controlar clientes usando o sistema de permissão do Web PubSub.
No Web PubSub, um cliente pode executar os seguintes tipos de operações com subprotocolo:
- Enviar eventos para o servidor.
- Publicar mensagens em um grupo.
- Junte-se (subscreva) um grupo.
Enviar um evento para o servidor é a operação padrão do cliente. Nenhum protocolo é usado, por isso é sempre permitido. Para publicar e se inscrever em um grupo, o cliente precisa obter permissão. Há duas maneiras de o servidor conceder permissão aos clientes:
- Especifique funções quando um cliente estiver conectado (função é um conceito para representar permissões iniciais quando um cliente está conectado).
- Use uma API para conceder permissão a um cliente depois que ele estiver conectado.
Para obter permissão para ingressar em um grupo, o cliente ainda precisa ingressar no grupo usando a mensagem "ingressar no grupo" depois de obter a permissão. Como alternativa, o servidor pode usar uma API para adicionar o cliente a um grupo, mesmo que ele não tenha a permissão de associação.
Agora vamos usar esse sistema de permissão para adicionar um novo recurso à sala de chat. Você adiciona um novo tipo de usuário chamado administrador à sala de chat. Você permite que o administrador envie mensagens do sistema (mensagens que começam com "[SYSTEM]") diretamente do cliente.
Primeiro, você precisa separar as mensagens do sistema e do usuário em dois grupos diferentes para poder controlar suas permissões separadamente.
Altere server.js
para enviar mensagens diferentes para grupos diferentes:
let handler = new WebPubSubEventHandler(hubName, {
path: '/eventhandler',
handleConnect: (req, res) => {
res.success({
groups: ['system', 'message'],
});
},
onConnected: req => {
console.log(`${req.context.userId} connected`);
serviceClient.group('system').sendToAll(`${req.context.userId} joined`, { contentType: 'text/plain' });
},
handleUserEvent: (req, res) => {
if (req.context.eventName === 'message') {
serviceClient.group('message').sendToAll({
user: req.context.userId,
message: req.data
});
}
res.success();
}
});
O código anterior usa WebPubSubServiceClient.group().sendToAll()
para enviar a mensagem para um grupo em vez do hub.
Como a mensagem agora é enviada para grupos, você precisa adicionar clientes a grupos para que eles possam continuar recebendo mensagens. Use o handleConnect
manipulador para adicionar clientes a grupos.
Nota
handleConnect
é acionado quando um cliente está tentando se conectar ao Web PubSub. Nesse manipulador, você pode retornar grupos e funções, para que o serviço possa adicionar uma conexão a grupos ou conceder funções, assim que a conexão for estabelecida. O serviço também pode usar res.fail()
para negar a conexão.
Para acionar handleConnect
o , vá para as configurações do manipulador de eventos no portal do Azure e selecione conectar em eventos do sistema.
Você também precisa atualizar o HTML do cliente, porque agora o servidor envia mensagens JSON em vez de texto sem formatação:
let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
ws.onopen = () => console.log('connected');
ws.onmessage = event => {
let m = document.createElement('p');
let message = JSON.parse(event.data);
switch (message.type) {
case 'message':
if (message.group === 'system') m.innerText = `[SYSTEM] ${message.data}`;
else if (message.group === 'message') m.innerText = `[${message.data.user}] ${message.data.message}`;
break;
}
messages.appendChild(m);
};
let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
if (e.charCode !== 13) return;
ws.send(JSON.stringify({
type: 'event',
event: 'message',
dataType: 'text',
data: message.value
}));
message.value = '';
});
Em seguida, altere o código do cliente para enviar ao grupo do sistema quando os usuários selecionarem a mensagem do sistema:
<button id="system">system message</button>
...
<script>
(async function() {
...
let system = document.querySelector('#system');
system.addEventListener('click', e => {
ws.send(JSON.stringify({
type: 'sendToGroup',
group: 'system',
dataType: 'text',
data: message.value
}));
message.value = '';
});
})();
</script>
Por padrão, o cliente não tem permissão para enviar para nenhum grupo. Atualize o código do servidor para conceder permissão ao usuário administrador (para simplificar, a ID do administrador é fornecida como um argumento de linha de comando).
app.get('/negotiate', async (req, res) => {
...
if (req.user.username === process.argv[2]) options.claims = { role: ['webpubsub.sendToGroup.system'] };
let token = await serviceClient.getClientAccessToken(options);
});
Agora corra node server <admin-id>
. Você vê que pode enviar uma mensagem do sistema para cada cliente quando entrar como <admin-id>
.
Mas se você entrar como um usuário diferente, quando você selecionar a mensagem do sistema, nada acontece. Você pode esperar que o serviço forneça um erro para informar que a operação não é permitida. Para fornecer esse feedback, você pode definir ackId
quando está publicando a mensagem. Sempre que ackId
é especificado, Web PubSub retorna uma mensagem com uma correspondência ackId
para indicar se a operação foi bem-sucedida ou não.
Altere o código de envio de uma mensagem do sistema para o seguinte código:
let ackId = 0;
system.addEventListener('click', e => {
ws.send(JSON.stringify({
type: 'sendToGroup',
group: 'system',
ackId: ++ackId,
dataType: 'text',
data: message.value
}));
message.value = '';
});
Altere também o código de processamento de mensagens para lidar com uma ack
mensagem:
ws.onmessage = event => {
...
switch (message.type) {
case 'ack':
if (!message.success && message.error.name === 'Forbidden') m.innerText = 'No permission to send system message';
break;
}
};
Agora execute novamente o servidor e entre como um usuário diferente. Você vê uma mensagem de erro quando tenta enviar uma mensagem do sistema.
O exemplo de código completo deste tutorial pode ser encontrado no GitHub.
Próximos passos
Este tutorial fornece uma ideia básica de como se conectar ao serviço Web PubSub e como publicar mensagens para clientes conectados usando o subprotocolo.
Para saber mais sobre como usar o serviço Web PubSub, leia os outros tutoriais disponíveis na documentação.