Compartilhar via


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.

  1. Instale as dependências.

    npm install --save cookie-parser
    npm install --save express-session
    npm install --save passport
    npm install --save passport-github2
    
  2. Localize o arquivo server.js em seu diretório e habilite a autenticação do GitHub adicionando o seguinte código a server.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:

    1. /auth/github redirecionará você ao github.com para a entrada.
    2. 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 em passport.use()).
    3. 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:

    1. Acesse https://www.github.com, abra seu perfil e selecione Configurações>Configurações do desenvolvedor.
    2. Acesse Aplicativos OAuth e escolha Novo Aplicativo OAuth.
    3. 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.
    4. 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.

  3. 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.