Tutorial: Adicionar autenticação e permissões ao seu aplicativo ao usar o Azure Web PubSub
Em Criar um aplicativo de chat, você aprendeu a usar APIs do WebSocket para enviar e receber dados com o Azure Web PubSub. Observe que, para simplificar, ele não requer nenhuma autenticação. Embora o Azure Web PubSub exija que um token de acesso seja conectado, a API negotiate
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, normalmente, o ideal é que o usuário se conecte primeiro para usar seu aplicativo. Neste tutorial, você aprenderá a integrar o Web PubSub ao sistema de autenticação e autorização do seu aplicativo para torná-lo mais seguro.
Encontre o exemplo de código completo deste tutorial no GitHub.
Neste tutorial, você aprende a:
- Habilitar a autenticação no GitHub
- Adicionar middleware de autenticação ao aplicativo
- Adicionar permissões aos clientes
Importante
Cadeias de conexão brutas aparecem neste artigo somente para fins de demonstração.
Uma cadeia de conexão inclui as informações de autorização necessárias para que o seu aplicativo acesse o serviço Azure Web PubSub. A chave de acesso dentro da cadeia de conexão é semelhante a uma senha raiz para o serviço. Em ambientes de produção, sempre proteja 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
.
Evite distribuir chaves de acesso para outros usuários, fazer hard-coding com elas ou salvá-las em qualquer lugar em texto sem formatação que seja acessível a outras pessoas. Gire suas chaves se você acredita que elas podem ter sido comprometidas.
Adicionar autenticação ao aplicativo de sala de chat
Este tutorial reutiliza o aplicativo de chat criado em Criar um aplicativo de chat. Clone também o código de exemplo completo do aplicativo de chat do GitHub.
Neste tutorial, você adicionará a autenticação ao aplicativo de chat e o integrará ao Web PubSub.
Primeiro, adicione a autenticação do GitHub à sala de chat para que o usuário possa usar a conta do GitHub para se conectar.
Instale as dependências.
npm install --save cookie-parser npm install --save express-session npm install --save passport npm install --save passport-github2
Localize o arquivo
server.js
em seu diretório e habilite a autenticação do GitHub adicionando o seguinte código aserver.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 o Passport.js para habilitar a autenticação do GitHub. Esta é uma ilustração simples de como isso funciona:
/auth/github
redirecionará você ao github.com para a entrada.- Depois que você entrar, GitHub redirecionará você a
/auth/github/callback
com um código, de modo que o seu aplicativo conclua a autenticação. (Para ver como o perfil retornado do GitHub é verificado e persistido no servidor, confira o retorno de chamada verificado empassport.use()
). - Após a conclusão da autenticação, você será redirecionado à home page (
/
) do site.
Para obter mais detalhes sobre o OAuth do GitHub e o Passport.js, consulte os seguintes artigos:
Para testar isso, primeiro você precisa criar um aplicativo GitHub OAuth:
- Acesse https://www.github.com, abra seu perfil e selecione Configurações>Configurações do desenvolvedor.
- Acesse Aplicativos OAuth e escolha Novo Aplicativo OAuth.
- Preencha o nome do aplicativo e a URL da home page (a URL pode ser qualquer uma que desejar) e defina URL de retorno de chamada de autorização como
http://localhost:8080/auth/github/callback
. Essa URL corresponde à API de retorno de chamada que você expôs no servidor. - Depois que o aplicativo for registrado, copie a ID do cliente e selecione Gerar um segredo do cliente.
Cadeias de conexão brutas aparecem neste artigo somente para fins de demonstração. Em ambientes de produção, sempre proteja 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
.Execute o comando abaixo para testar as configurações; não se esqueça de substituir
<connection-string>
,<client-id>
e<client-secret>
pelos seus valores.export WebPubSubConnectionString="<connection-string>" export GitHubClientId="<client-id>" export GitHubClientSecret="<client-secret>" node server
Agora, abra
http://localhost:8080/auth/github
. Você será redirecionado ao GitHub para a entrada. Depois de se conectar, você será redirecionado ao aplicativo de chat.Atualize a sala de chat para usar a identidade obtida do GitHub, em vez de solicitar um nome de usuário ao usuário.
Atualize
public/index.html
para chamar/negotiate
diretamente sem passar uma 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 usuário estiver conectado, a solicitação carregará automaticamente a identidade do usuário por meio de um cookie. Então, você só precisa verificar se o usuário existe no objeto
req
e adicionar o nome de usuário ao token de acesso do 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á a mensagem "Não autorizado" na primeira vez que abrir a sala de chat. Selecione o link de credenciais para se conectar e verá que ele funciona como antes.
Trabalhar com permissões
Nos tutoriais anteriores, você aprendeu a usar o WebSocket.send()
para publicar mensagens diretamente em outros clientes usando o subprotocolo. Em um aplicativo real, talvez você não queira que o cliente faça nenhuma publicação ou assinatura em nenhum grupo sem o controle de permissão. Nesta seção, você verá como controlar os clientes usando o sistema de permissão do Web PubSub.
No Web PubSub, um cliente pode executar os seguintes tipos de operações com um subprotocolo:
- Enviar eventos para o servidor.
- Publicar mensagens em um grupo.
- Ingressar (assinar) em um grupo.
Enviar um evento para o servidor é a operação padrão do cliente. Nenhum protocolo é usado. Portanto, isso sempre é permitido. Para fazer uma publicação ou uma assinatura em um grupo, o cliente precisa obter uma permissão. Há duas maneiras pelas quais servidor concede permissão aos clientes:
- Especificar funções quando um cliente está conectado (a função é um conceito usado para representar as permissões iniciais quando um cliente está conectado).
- Usar uma API para conceder permissão a um cliente depois que ele estiver conectado.
Para obter a 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 para ingressar nele.
Agora, vamos usar esse sistema de permissão para adicionar um novo recurso à sala de chat. Você adicionará um novo tipo de usuário chamado administrador à sala de chat. Você permitirá que o administrador envie mensagens do sistema (mensagens que começam com "[SYSTEM]") diretamente no cliente.
Primeiro, você precisa separar as mensagens do sistema e do usuário em dois grupos diferentes para que possa controlar as permissões deles 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ê precisará adicionar clientes aos grupos para que eles possam continuar recebendo mensagens. Use o manipulador handleConnect
para adicionar clientes aos grupos.
Observação
handleConnect
é disparado quando um cliente tenta se conectar ao Web PubSub. Nesse manipulador, você pode retornar grupos e funções, de modo que o serviço possa adicionar uma conexão aos grupos ou conceder funções, assim que a conexão é estabelecida. O serviço também pode usar res.fail()
para negar a conexão.
Para disparar handleConnect
, acesse 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, pois 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 envio ao grupo do sistema quando os usuários selecionarem 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 de envio a 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 da 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 execute node server <admin-id>
. Você verá que pode enviar uma mensagem do sistema para cada cliente ao entrar como <admin-id>
.
Mas se você entrar como um usuário diferente, ao selecionar mensagem do sistema, nada acontecerá. Você pode esperar que o serviço gere um erro para permitir que você saiba que a operação não é permitida. Para fornecer esses comentários, defina ackId
ao publicar a mensagem. Sempre que ackId
for especificado, o Web PubSub retornará uma mensagem com uma ackId
correspondente para indicar se a operação foi bem-sucedida.
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 do processamento de mensagens para tratar uma mensagem ack
:
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 o servidor novamente e entre como outro usuário. Você verá uma mensagem de erro quando estiver tentando enviar uma mensagem do sistema.
O exemplo de código completo deste tutorial pode ser encontrado no GitHub.
Próximas etapas
Este tutorial fornece uma ideia básica de como se conectar ao serviço Web PubSub e como publicar mensagens nos 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.