Esercitazione: Aggiungere l'autenticazione e le autorizzazioni all'applicazione quando si usa Azure Web PubSub

In Creare un'app di chat si è appreso come usare le API WebSocket per inviare e ricevere dati con Web PubSub di Azure. Si noti che, per semplicità, non richiede alcuna autenticazione. Anche se Azure Web PubSub richiede la connessione di un token di accesso, l'API negotiate usata nell'esercitazione per generare il token di accesso non richiede l'autenticazione. Chiunque può chiamare questa API per ottenere un token di accesso.

In un'applicazione reale, in genere si vuole che l'utente ese segno prima di poter usare l'applicazione. In questa esercitazione si apprenderà come integrare Web PubSub con il sistema di autenticazione e autorizzazione dell'applicazione per renderlo più sicuro.

L'esempio di codice completo di questa esercitazione è disponibile in GitHub.

In questa esercitazione apprenderai a:

  • Abilitare l'autenticazione di GitHub
  • Aggiungere il middleware di autenticazione all'applicazione
  • Aggiungere autorizzazioni ai client

Aggiungere l'autenticazione all'app chat room

Questa esercitazione riutilizza l'applicazione di chat creata in Creare un'app di chat. È anche possibile clonare l'esempio di codice completo per l'app di chat da GitHub.

In questa esercitazione si aggiunge l'autenticazione all'applicazione chat e la si integra con Web PubSub.

Prima di tutto, aggiungere l'autenticazione GitHub alla chat room in modo che l'utente possa usare un account GitHub per accedere.

  1. Installare le dipendenze.

    npm install --save cookie-parser
    npm install --save express-session
    npm install --save passport
    npm install --save passport-github2
    
  2. Trovare il server.js file nella directory e abilitare l'autenticazione di GitHub aggiungendo il codice seguente 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: '/' }));
    

    Il codice precedente usa Passport.js per abilitare l'autenticazione di GitHub. Ecco una semplice illustrazione del funzionamento:

    1. /auth/github reindirizza a github.com per l'accesso.
    2. Dopo l'accesso, GitHub reindirizza l'utente a /auth/github/callback con un codice per completare l'autenticazione dell'applicazione. Per vedere come il profilo restituito da GitHub viene verificato e salvato in modo permanente nel server, vedere il callback verificato in passport.use().
    3. Al termine dell'autenticazione, si viene reindirizzati alla home page (/) del sito.

    Per altre informazioni su GitHub OAuth e Passport.js, vedere gli articoli seguenti:

    Per eseguire questo test, è prima necessario creare un'app OAuth gitHub:

    1. Passare a https://www.github.com, aprire il profilo e selezionare Impostazioni> Impostazioni sviluppatore.
    2. Passare ad App OAuth e selezionare Nuova app OAuth.
    3. Immettere il nome dell'applicazione e l'URL della home page (l'URL può essere qualsiasi elemento desiderato) e impostare URL di callback dell'autorizzazione su http://localhost:8080/auth/github/callback. Questo URL corrisponde all'API di callback esposta nel server.
    4. Dopo aver registrato l'applicazione, copiare l'ID client e selezionare Genera un nuovo segreto client.

    Eseguire il comando seguente per testare le impostazioni, non dimenticare di sostituire <connection-string>, <client-id>e <client-secret> con i valori.

    export WebPubSubConnectionString="<connection-string>"
    export GitHubClientId="<client-id>"
    export GitHubClientSecret="<client-secret>"
    node server
    

    http://localhost:8080/auth/githubAprire ora . Si viene reindirizzati a GitHub per accedere. Dopo aver eseguito l'accesso, si viene reindirizzati all'applicazione di chat.

  3. Aggiornare la chat room per usare l'identità ottenuta da GitHub, anziché richiedere all'utente un nome utente.

    Eseguire l'aggiornamento public/index.html alla chiamata /negotiate diretta senza passare un ID utente.

    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 un utente ha eseguito l'accesso, la richiesta porta automaticamente l'identità dell'utente tramite un cookie. È quindi sufficiente verificare se l'utente esiste nell'oggetto req e aggiungere il nome utente al token di accesso PubSub Web:

    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
      });
    });
    

    Eseguire di nuovo il server e viene visualizzato un messaggio "non autorizzato" per la prima volta che si apre la chat room. Selezionare il collegamento di accesso per accedere e quindi visualizzarlo come prima.

Usare le autorizzazioni

Nelle esercitazioni precedenti si è appreso come usare WebSocket.send() per pubblicare direttamente messaggi in altri client usando il sottoprotocolo. In un'applicazione reale potrebbe non essere necessario che il client sia in grado di pubblicare o sottoscrivere un gruppo senza controllo delle autorizzazioni. In questa sezione viene illustrato come controllare i client usando il sistema di autorizzazioni di Web PubSub.

In Web PubSub un client può eseguire i tipi di operazioni seguenti con sottoprotocolo:

  • Inviare eventi al server.
  • Pubblicare messaggi in un gruppo.
  • Partecipare (sottoscrivere) un gruppo.

L'invio di un evento al server è l'operazione predefinita del client. Non viene usato alcun protocollo, quindi è sempre consentito. Per pubblicare e sottoscrivere un gruppo, il client deve ottenere l'autorizzazione. Esistono due modi per consentire al server di concedere l'autorizzazione ai client:

  • Specificare i ruoli quando un client è connesso (il ruolo è un concetto per rappresentare le autorizzazioni iniziali quando un client è connesso).
  • Usare un'API per concedere l'autorizzazione a un client dopo la connessione.

Per l'autorizzazione per l'aggiunta a un gruppo, il client deve comunque unirsi al gruppo usando il messaggio "join group" dopo che ottiene l'autorizzazione. In alternativa, il server può usare un'API per aggiungere il client a un gruppo, anche se non dispone dell'autorizzazione di join.

A questo punto si userà questo sistema di autorizzazioni per aggiungere una nuova funzionalità alla chat room. Si aggiunge un nuovo tipo di utente denominato amministratore alla chat room. L'amministratore può inviare messaggi di sistema (messaggi che iniziano con "[SYSTEM]") direttamente dal client.

Prima di tutto, è necessario separare i messaggi di sistema e utente in due gruppi diversi in modo da poter controllare separatamente le relative autorizzazioni.

Modificare server.js per inviare messaggi diversi a gruppi diversi:

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();
  }
});

Il codice precedente usa WebPubSubServiceClient.group().sendToAll() per inviare il messaggio a un gruppo anziché all'hub.

Poiché il messaggio viene ora inviato ai gruppi, è necessario aggiungere client ai gruppi in modo che possano continuare a ricevere messaggi. Usare il handleConnect gestore per aggiungere client ai gruppi.

Nota

handleConnect viene attivato quando un client tenta di connettersi a Web PubSub. In questo gestore è possibile restituire gruppi e ruoli, in modo che il servizio possa aggiungere una connessione ai gruppi o concedere ruoli, non appena viene stabilita la connessione. Il servizio può anche usare res.fail() per negare la connessione.

Per attivare handleConnect, passare alle impostazioni del gestore eventi nel portale di Azure e selezionare Connetti negli eventi di sistema.

È anche necessario aggiornare il codice HTML del client, perché ora il server invia messaggi JSON invece di testo normale:

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 = '';
});

Modificare quindi il codice client da inviare al gruppo di sistema quando gli utenti selezionano il messaggio di 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>

Per impostazione predefinita, il client non dispone dell'autorizzazione per l'invio ad alcun gruppo. Aggiornare il codice del server per concedere l'autorizzazione per l'utente amministratore (per semplicità, l'ID dell'amministratore viene fornito come argomento della riga di 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);
});

node server <admin-id>Eseguire ora . Si noterà che è possibile inviare un messaggio di sistema a ogni client quando si accede come <admin-id>.

Tuttavia, se si accede come utente diverso, quando si seleziona il messaggio di sistema, non accade nulla. È possibile che il servizio restituisca un errore per segnalare che l'operazione non è consentita. Per fornire questo feedback, è possibile impostare ackId quando si pubblica il messaggio. Ogni volta che ackId viene specificato, Web PubSub restituisce un messaggio corrispondente ackId per indicare se l'operazione è riuscita o meno.

Modificare il codice di invio di un messaggio di sistema al codice seguente:

let ackId = 0;
system.addEventListener('click', e => {
  ws.send(JSON.stringify({
    type: 'sendToGroup',
    group: 'system',
    ackId: ++ackId,
    dataType: 'text',
    data: message.value
    }));
  message.value = '';
});

Modificare anche il codice di elaborazione dei messaggi per gestire un ack messaggio:

ws.onmessage = event => {
  ...
  switch (message.type) {
    case 'ack':
      if (!message.success && message.error.name === 'Forbidden') m.innerText = 'No permission to send system message';
      break;
  }
};

Eseguire di nuovo il server e accedere come utente diverso. Viene visualizzato un messaggio di errore quando si sta provando a inviare un messaggio di sistema.

L'esempio di codice completo di questa esercitazione è disponibile in GitHub.

Passaggi successivi

Questa esercitazione offre un'idea di base su come connettersi al servizio Web PubSub e su come pubblicare messaggi ai client connessi usando il sottoprotocolo.

Per altre informazioni sull'uso del servizio Web PubSub, leggere le altre esercitazioni disponibili nella documentazione.