Share 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 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

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.

  1. Instale 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 em seu diretório e habilite a autenticação do GitHub adicionando o server.js seguinte código ao 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. Aqui está uma ilustração simples de como funciona:

    1. /auth/github redireciona para github.com para entrar.
    2. 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 em passport.use().)
    3. 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 o Passport.js, consulte os seguintes artigos:

    Para testar isso, você precisa primeiro criar um aplicativo GitHub OAuth:

    1. Vá para https://www.github.com, abra seu perfil e selecione Configurações Configurações>do desenvolvedor.
    2. Vá para Aplicativos OAuth e selecione Novo Aplicativo OAuth.
    3. 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.
    4. Depois que o aplicativo for registrado, copie o ID do cliente e selecione Gerar um novo segredo do cliente.

    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.

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