Nutzer mit Node.js authentifizieren

In diesem Teil der Anleitung zu Node.js Bookshelf wird erläutert, wie Sie einen Log-in-Fluss für Nutzer erstellen. Außerdem erfahren Sie, wie Sie mithilfe von Profilinformationen personalisierte Funktionen für Nutzer bereitstellen.

Mithilfe der Google Identity Platform können Sie bequem auf Informationen über Ihre Nutzer zugreifen und dabei sicherstellen, dass ihre Log-in-Daten von Google sicher verwaltet werden. Mit OAuth 2.0 ist es einfach, für alle Nutzer der Anwendung einen Anmeldevorgang bereitzustellen. Außerdem erhält Ihre Anwendung Zugriff auf die grundlegenden Profilinformationen authentifizierter Nutzer.

Diese Seite ist Teil einer mehrseitigen Anleitung. Rufen Sie die Node.js Bookshelf App auf, um die Dokumentation von Anfang an durchzugehen und Anleitungen zur Einrichtung zu erhalten.

Client-ID für Webanwendung erstellen

Eine Client-ID für eine Webanwendung ermöglicht der Anwendung, Nutzer zu autorisieren und in ihrem Namen auf Google APIs zuzugreifen.

  1. Wechseln Sie in der Google Cloud Platform Console zu den Anmeldedaten.

    Anmeldedaten aufrufen

  2. Klicken Sie auf OAuth-Zustimmungsbildschirm. Geben Sie für den Produktnamen Node.js Bookshelf App ein.

  3. Geben Sie unter Autorisierte Domains den App Engine-Anwendungsnamen als [YOUR_PROJECT_ID].appspot.com ein. Ersetzen Sie dabei [YOUR_PROJECT_ID] durch die GCP-Projekt-ID.

  4. Füllen Sie alle anderen infrage kommenden, optionalen Felder aus. Klicken Sie auf Speichern.

  5. Klicken Sie auf Anmeldedaten erstellen > OAuth-Client-ID.

  6. Wählen Sie in der Drop-down-Liste Anwendungstyp die Option Webanwendungen aus.

  7. Geben Sie im Feld Name den Wert Node.js Bookshelf Client ein.

  8. Geben Sie im Feld Autorisierte Weiterleitungs-URIs nacheinander die folgenden URLs ein.

    http://localhost:8080/auth/google/callback
    http://[YOUR_PROJECT_ID].appspot.com/auth/google/callback
    https://[YOUR_PROJECT_ID].appspot.com/auth/google/callback

  9. Klicken Sie auf Erstellen.

  10. Kopieren Sie die Client-ID und den Clientschlüssel und speichern Sie diese zur späteren Verwendung.

Einstellungen konfigurieren

Kopieren Sie die Datei config.json aus dem Abschnitt Cloud Storage dieser Anleitung in das Verzeichnis nodejs-getting-started/4-auth. Fügen Sie der kopierten Datei die folgenden Zeilen hinzu:

"OAUTH2_CLIENT_ID": "[YOUR_OAUTH2_CLIENT_ID]",
"OAUTH2_CLIENT_SECRET": "[YOUR_OAUTH2_CLIENT_SECRET]",
"OAUTH2_CALLBACK": "http://localhost:8080/auth/google/callback",
  1. Ersetzen Sie [YOUR_OAUTH2_CLIENT_ID] und [YOUR_OAUTH2_CLIENT_SECRET] durch die Anwendungsclient-ID und das Secret, die Sie zuvor erstellt haben.

Abhängigkeiten installieren

Installieren Sie Abhängigkeiten im Verzeichnis nodejs-getting-started/4-auth mit npm:

    npm install

App auf lokalem Computer ausführen

  1. Starten Sie einen lokalen Webserver mit npm:

        npm start
    
  1. Geben Sie im Webbrowser die folgende Adresse ein:

    http://localhost:8080

Sie können nun auf den Webseiten der App stöbern, sich mit Ihrem Google-Konto anmelden, Bücher hinzufügen und sich die Bücher ansehen, die Sie über den Link Meine Bücher in der oberen Navigationsleiste hinzugefügt haben.

App in der App Engine-Standardumgebung bereitstellen

  1. Ändern Sie zur Aktivierung der Onlineauthentifizierung die Zeile OAUTH2_CALLBACK von config.json im Verzeichnis nodejs-getting-started/4-auth in den folgenden Wert. Ersetzen Sie [YOUR_PROJECT_ID] durch Ihre Projekt-ID:

    "OAUTH2_CALLBACK": "https://[YOUR_PROJECT_ID].appspot.com/auth/google/callback"
    
  2. Erstellen Sie die Beispielanwendung aus dem Verzeichnis nodejs-getting-started/4-auth:

    gcloud app deploy
    
  3. Geben Sie im Webbrowser die folgende Adresse ein: Ersetzen Sie [YOUR_PROJECT_ID] durch Ihre Projekt-ID:

    https://[YOUR_PROJECT_ID].appspot.com
    

Wenn Sie die App aktualisieren, können Sie die aktualisierte Version mit demselben Befehl bereitstellen, den Sie bei der ersten Bereitstellung der App verwendet haben. Bei der neuen Bereitstellung wird eine neue Version der Anwendung erstellt und zur Standardversion hochgestuft. Die älteren Versionen der App werden beibehalten. Standardmäßig wird die App Engine-Standardumgebung auf 0 Instanzen skaliert, wenn für eine Version kein Datenverkehr eingeht. Ungenutzte Versionen sollten daher nichts kosten. Alle diese App-Versionen bleiben jedoch kostenpflichtige Ressourcen.

Weitere Informationen zur Bereinigung abrechenbarer Ressourcen, einschließlich der App-Versionen, die keine Standardversionen sind, finden Sie im Abschnitt Bereinigen im letzten Schritt dieser Anleitung.

Anwendungsstruktur

Im folgenden Diagramm sehen Sie, welche Anwendungskomponenten es gibt und wie sie miteinander zusammenhängen.

Auth-Beispielstruktur

Erläuterungen zum Code

In diesem Abschnitt werden der Anwendungscode und dessen Funktionsweise erläutert.

Informationen zu Sitzungen

Damit Ihre Anwendung Nutzer authentifizieren kann, benötigen Sie eine Möglichkeit, Informationen über den aktuellen Nutzer in einer Sitzung zu speichern. Express verfügt über eine speicherbasierte Implementierung. Diese ist aber nicht für Anwendungen geeignet, die von mehreren Instanzen bedient werden können, weil sich die in einer Instanz aufgezeichnete Sitzung von der Sitzung in anderen Instanzen unterscheiden kann. Das Bookshelf-Beispiel verwendet Cloud Datastore zum Speichern von Sitzungsdaten.

// Configure the session and session storage.
const sessionConfig = {
  resave: false,
  saveUninitialized: false,
  secret: config.get('SECRET'),
  signed: true,
  store: new DatastoreStore({
    dataset: new Datastore({kind: 'express-sessions'}),
  }),
};

app.use(session(sessionConfig));

Nutzer authentifizieren

Im Bookshelf-Beispiel wird Passport.js zur Verwaltung der Nutzerauthentifizierung verwendet. Passport.js erfordert einige Einrichtungsschritte, bevor Nutzer authentifiziert werden können:

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

function extractProfile(profile) {
  let imageUrl = '';
  if (profile.photos && profile.photos.length) {
    imageUrl = profile.photos[0].value;
  }
  return {
    id: profile.id,
    displayName: profile.displayName,
    image: imageUrl,
  };
}

// Configure the Google strategy for use by Passport.js.
//
// OAuth 2-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Google API on the user's behalf,
// along with the user's profile. The function must invoke `cb` with a user
// object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(
  new GoogleStrategy(
    {
      clientID: config.get('OAUTH2_CLIENT_ID'),
      clientSecret: config.get('OAUTH2_CLIENT_SECRET'),
      callbackURL: config.get('OAUTH2_CALLBACK'),
      accessType: 'offline',
      userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo',
    },
    (accessToken, refreshToken, profile, cb) => {
      // Extract the minimal profile information we need from the profile object
      // provided by Google
      cb(null, extractProfile(profile));
    }
  )
);

passport.serializeUser((user, cb) => {
  cb(null, user);
});
passport.deserializeUser((obj, cb) => {
  cb(null, obj);
});

Bereiche geben an, auf welche Elemente der Nutzerinformationen die Anwendung zugreifen kann. Google bietet mehrere Dienste mit verschiedenen Bereichen für jeden Dienst. Es gibt z. B. einen Bereich, der lediglich Lesezugriff auf Google Drive ermöglicht, während ein anderer Bereich Lese- und Schreibzugriff zulässt. Dieses Beispiel erfordert nur die grundlegenden Bereiche email und profile, durch die der Anwendung Zugriff auf die E-Mail-Adresse und die grundlegenden Profilinformationen des Nutzers gewährt wird.

Die Nutzerauthentifizierung umfasst im Wesentlichen zwei Schritte, die zusammen als Webdienstfluss bezeichnet werden.

  1. Nutzer zum Google-Autorisierungsdienst weiterleiten
  2. Die Antwort verarbeiten, wenn Google den Nutzer zu Ihrer Anwendung zurückleitet

Zu Google weiterleiten

Der Nutzer wird von der Anwendung zum Google-Autorisierungsdienst weitergeleitet. Die Anwendung generiert die URL mithilfe Ihrer Client-ID und der von der Anwendung benötigten Bereiche.

router.get(
  // Login url
  '/auth/login',

  // Save the url of the user's current page so the app can redirect back to
  // it after authorization
  (req, res, next) => {
    if (req.query.return) {
      req.session.oauth2return = req.query.return;
    }
    next();
  },

  // Start OAuth 2 flow using Passport.js
  passport.authenticate('google', {scope: ['email', 'profile']})
);

Ihre Anwendung leitet den Nutzer dann zur generierten URL weiter. Der Nutzer wird aufgefordert, der Anwendung den Zugriff auf die angegebenen Bereiche zu gestatten. Nach seiner Zustimmung wird der Nutzer zu Ihrer Anwendung zurückgeleitet.

Autorisierungsantwort verarbeiten

Google sendet den Nutzer zusammen mit einem Autorisierungscode zur Anwendung zurück. Mithilfe dieses Codes, einschließlich des Clientschlüssels, können die Anmeldedaten des Nutzers und andere Informationen über den Nutzer abgerufen werden. Dieser Schritt wird vollständig auf dem Server ausgeführt, ohne dass der Nutzer weitergeleitet werden muss. Durch diesen zusätzlichen Schritt wird überprüft, ob der Autorisierungscode echt ist und ob die Anmeldedaten tatsächlich von der Anwendung angefordert werden. Mit Passport.js ist dies ganz einfach:

router.get(
  // OAuth 2 callback url. Use this url to configure your OAuth client in the
  // Google Developers console
  '/auth/google/callback',

  // Finish OAuth 2 flow using Passport.js
  passport.authenticate('google'),

  // Redirect back to the original page, if any
  (req, res) => {
    const redirect = req.session.oauth2return || '/';
    delete req.session.oauth2return;
    res.redirect(redirect);
  }
);

Nach dem Erhalt der Anmeldedaten können Sie Informationen über den Nutzer speichern. Passport.js serialisiert den Nutzer automatisch für die Sitzung. Sobald sich die Informationen des Nutzers in der Sitzung befinden, können Sie einige Middlewarefunktionen erstellen, um die Arbeit mit der Authentifizierung zu erleichtern:

// Middleware that requires the user to be logged in. If the user is not logged
// in, it will redirect the user to authorize the application and then return
// them to the original URL they requested.
function authRequired(req, res, next) {
  if (!req.user) {
    req.session.oauth2return = req.originalUrl;
    return res.redirect('/auth/login');
  }
  next();
}

// Middleware that exposes the user's profile as well as login/logout URLs to
// any templates. These are available as `profile`, `login`, and `logout`.
function addTemplateVariables(req, res, next) {
  res.locals.profile = req.user;
  res.locals.login = `/auth/login?return=${encodeURIComponent(
    req.originalUrl
  )}`;
  res.locals.logout = `/auth/logout?return=${encodeURIComponent(
    req.originalUrl
  )}`;
  next();
}

Sie können die von der Middleware bereitgestellten Informationen in Ihren Vorlagen verwenden, um dem Nutzer zu zeigen, ob er angemeldet oder abgemeldet ist:

if profile
  if profile.image
    img.img-circle(src=profile.image, width=24)
  span #{profile.displayName}  
    a(href=logout) (logout)
else
  a(href=login) Login

Personalisierung

Nachdem Ihnen nun die Informationen des angemeldeten Nutzers über die Middleware zur Verfügung stehen, können Sie verfolgen, welcher Nutzer der Datenbank welches Buch hinzugefügt hat.

router.post(
  '/add',
  images.multer.single('image'),
  images.sendUploadToGCS,
  (req, res, next) => {
    const data = req.body;

    // If the user is logged in, set them as the creator of the book.
    if (req.user) {
      data.createdBy = req.user.displayName;
      data.createdById = req.user.id;
    } else {
      data.createdBy = 'Anonymous';
    }

    // Was an image uploaded? If so, we'll use its public URL
    // in cloud storage.
    if (req.file && req.file.cloudStoragePublicUrl) {
      data.imageUrl = req.file.cloudStoragePublicUrl;
    }

    // Save the data to the database.
    getModel().create(data, (err, savedData) => {
      if (err) {
        next(err);
        return;
      }
      res.redirect(`${req.baseUrl}/${savedData.id}`);
    });
  }
);

Nachdem Sie nun über diese Informationen in der Datenbank verfügen, können Sie dem Nutzer zeigen, welche Bücher er selbst der Datenbank hinzugefügt hat.

// Use the oauth2.required middleware to ensure that only logged-in users
// can access this handler.
router.get('/mine', oauth2.required, (req, res, next) => {
  getModel().listBy(
    req.user.id,
    10,
    req.query.pageToken,
    (err, entities, cursor) => {
      if (err) {
        next(err);
        return;
      }
      res.render('books/list.pug', {
        books: entities,
        nextPageToken: cursor,
      });
    }
  );
});

In diesem Code wird eine neue Modellmethode namens listBy verwendet. Die Implementierung hängt davon ab, welches Datenbank-Back-End Sie gewählt haben.

Datenspeicher

function listBy(userId, limit, token, cb) {
  const q = ds
    .createQuery([kind])
    .filter('createdById', '=', userId)
    .limit(limit)
    .start(token);

  ds.runQuery(q, (err, entities, nextQuery) => {
    if (err) {
      cb(err);
      return;
    }
    const hasMore =
      nextQuery.moreResults !== Datastore.NO_MORE_RESULTS
        ? nextQuery.endCursor
        : false;
    cb(null, entities.map(fromDatastore), hasMore);
  });
}

Cloud SQL

function listBy(userId, limit, token, cb) {
  token = token ? parseInt(token, 10) : 0;
  connection.query(
    'SELECT * FROM `books` WHERE `createdById` = ? LIMIT ? OFFSET ?',
    [userId, limit, token],
    (err, results) => {
      if (err) {
        cb(err);
        return;
      }
      const hasMore = results.length === limit ? token + results.length : false;
      cb(null, results, hasMore);
    }
  );
}
Hat Ihnen diese Seite weitergeholfen? Teilen Sie uns Ihr Feedback mit:

Feedback geben zu...