GraphQL ist eine beliebte Alternative zur herkömmlichen RESTful-API-Architektur und bietet eine flexible und effiziente Datenabfrage- und Bearbeitungssprache für APIs. Mit Aufgrund der zunehmenden Akzeptanz wird es immer wichtiger, der Sicherheit von GraphQL-APIs Priorität einzuräumen, um Anwendungen vor unbefugtem Zugriff und potenziellen Daten zu schützen Verstöße.

Ein effektiver Ansatz zur Sicherung von GraphQL-APIs ist die Implementierung von JSON Web Tokens (JWTs). JWTs bieten eine sichere und effiziente Methode, um Zugriff auf geschützte Ressourcen zu gewähren und autorisierte Aktionen auszuführen und so eine sichere Kommunikation zwischen Clients und APIs zu gewährleisten.

Authentifizierung und Autorisierung in GraphQL-APIs

nicht wie REST-APIs, GraphQL-APIs verfügen normalerweise über einen einzelnen Endpunkt, der es Clients ermöglicht, in ihren Abfragen dynamisch unterschiedliche Datenmengen anzufordern. Während diese Flexibilität ihre Stärke ist, erhöht sie auch das Risiko potenzieller Sicherheitsangriffe, wie z. B. fehlerhafte Schwachstellen in der Zugangskontrolle.

instagram viewer

Um dieses Risiko zu mindern, ist es wichtig, robuste Authentifizierungs- und Autorisierungsprozesse zu implementieren, einschließlich der ordnungsgemäßen Definition von Zugriffsberechtigungen. Auf diese Weise stellen Sie sicher, dass nur autorisierte Benutzer auf geschützte Ressourcen zugreifen können, und verringern letztendlich das Risiko potenzieller Sicherheitsverletzungen und Datenverluste.

Den Code dieses Projekts finden Sie in seiner GitHub Repository.

Richten Sie einen Express.js Apollo Server ein

Apollo-Server ist eine weit verbreitete GraphQL-Serverimplementierung für GraphQL-APIs. Sie können damit ganz einfach GraphQL-Schemas erstellen, Resolver definieren und verschiedene Datenquellen für Ihre APIs verwalten.

Um einen Express.js Apollo Server einzurichten, erstellen und öffnen Sie einen Projektordner:

mkdir graphql-API-jwt
cd graphql-API-jwt

Führen Sie als Nächstes diesen Befehl aus, um ein neues Node.js-Projekt mit zu initialisieren npm, der Node-Paketmanager:

npm init --yes

Installieren Sie nun diese Pakete.

npm install apollo-server graphql mongoose jsonwebtokens dotenv

Erstellen Sie abschließend eine server.js Datei im Stammverzeichnis und richten Sie Ihren Server mit diesem Code ein:

const { ApolloServer } = require('apollo-server');
const mongoose = require('mongoose');
require('dotenv').config();

const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");

const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({ req }),
});

const MONGO_URI = process.env.MONGO_URI;

mongoose
.connect(MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
console.log("Connected to DB");
return server.listen({ port: 5000 });
})
.then((res) => {
console.log(`Server running at ${res.url}`);
})
.catch(err => {
console.log(err.message);
});

Der GraphQL-Server wird mit eingerichtet Typdefinitionen Und Resolver Parameter, die das Schema und die Vorgänge angeben, die die API verarbeiten kann. Der Kontext Die Option konfiguriert das req-Objekt für den Kontext jedes Resolvers, wodurch der Server auf anforderungsspezifische Details wie Header-Werte zugreifen kann.

Erstellen Sie eine MongoDB-Datenbank

Um zunächst die Datenbankverbindung herzustellen Erstellen Sie eine MongoDB-Datenbank oder Richten Sie einen Cluster auf MongoDB Atlas ein. Kopieren Sie dann die bereitgestellte Datenbankverbindungs-URI-Zeichenfolge und erstellen Sie eine .env Datei und geben Sie die Verbindungszeichenfolge wie folgt ein:

MONGO_URI=""

Definieren Sie das Datenmodell

Definieren Sie ein Datenmodell mit Mongoose. Erstelle eine neue models/user.js Datei und fügen Sie den folgenden Code ein:

const {model, Schema} = require('mongoose');

const userSchema = new Schema({
name: String,
password: String,
role: String
});

module.exports = model('user', userSchema);

Definieren Sie das GraphQL-Schema

In einer GraphQL-API definiert das Schema die Struktur der Daten, die abgefragt werden können, sowie die Gliederung die verfügbaren Vorgänge (Abfragen und Mutationen), die Sie ausführen können, um über die mit Daten zu interagieren API.

Um ein Schema zu definieren, erstellen Sie einen neuen Ordner im Stammverzeichnis Ihres Projekts und benennen Sie ihn graphql. Fügen Sie in diesem Ordner zwei Dateien hinzu: typeDefs.js Und Resolver.js.

Im typeDefs.js Fügen Sie in der Datei den folgenden Code ein:

const { gql } = require("apollo-server");

const typeDefs = gql`
type User {
id: ID!
name: String!
password: String!
role: String!
}
input UserInput {
name: String!
password: String!
role: String!
}
type TokenResult {
message: String
token: String
}
type Query {
users: [User]
}
type Mutation {
register(userInput: UserInput): User
login(name: String!, password: String!, role: String!): TokenResult
}
`;

module.exports = typeDefs;

Erstellen Sie Resolver für die GraphQL-API

Resolver-Funktionen bestimmen, wie Daten als Reaktion auf Client-Anfragen und Mutationen sowie auf andere im Schema definierte Felder abgerufen werden. Wenn ein Client eine Abfrage oder Mutation sendet, löst der GraphQL-Server die entsprechenden Resolver aus, um die erforderlichen Daten aus verschiedenen Quellen, wie Datenbanken oder APIs, zu verarbeiten und zurückzugeben.

Um Authentifizierung und Autorisierung mithilfe von JSON Web Tokens (JWTs) zu implementieren, definieren Sie Resolver für die Register- und Anmeldemutationen. Diese übernehmen die Prozesse der Benutzerregistrierung und -authentifizierung. Erstellen Sie dann einen Datenabruf-Abfrage-Resolver, auf den nur authentifizierte und autorisierte Benutzer zugreifen können.

Definieren Sie jedoch zunächst die Funktionen zum Generieren und Überprüfen der JWTs. Im Resolver.js Fügen Sie zunächst in der Datei die folgenden Importe hinzu.

const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;

Stellen Sie sicher, dass Sie den geheimen Schlüssel, den Sie zum Signieren von JSON-Web-Tokens verwenden, zur .env-Datei hinzufügen.

SECRET_KEY = '';

Um ein Authentifizierungstoken zu generieren, schließen Sie die folgende Funktion ein, die auch eindeutige Attribute für das JWT-Token angibt, z. B. die Ablaufzeit. Darüber hinaus können Sie basierend auf Ihren spezifischen Anwendungsanforderungen weitere Attribute einbeziehen, z. B. „ausgegeben zu einem bestimmten Zeitpunkt“.

functiongenerateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
 );

return token;
}

Implementieren Sie nun die Token-Verifizierungslogik, um die in nachfolgenden HTTP-Anfragen enthaltenen JWT-Token zu validieren.

functionverifyToken(token) {
if (!token) {
thrownewError('Token not provided');
}

try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
thrownewError('Invalid token');
}
}

Diese Funktion verwendet ein Token als Eingabe, überprüft seine Gültigkeit mithilfe des angegebenen geheimen Schlüssels und gibt das dekodierte Token zurück, wenn es gültig ist. Andernfalls wird ein Fehler ausgegeben, der auf ein ungültiges Token hinweist.

Definieren Sie die API-Resolver

Um die Resolver für die GraphQL-API zu definieren, müssen Sie die spezifischen Vorgänge beschreiben, die sie verwalten, in diesem Fall die Benutzerregistrierungs- und Anmeldevorgänge. Erstellen Sie zunächst eine Resolver Objekt, das die Resolver-Funktionen enthält, definieren Sie dann die folgenden Mutationsoperationen:

const resolvers = {
Mutation: {
register: async (_, { userInput: { name, password, role } }) => {
if (!name || !password || !role) {
thrownewError('Name password, and role required');
}

const newUser = new User({
name: name,
password: password,
role: role,
});

try {
const response = await newUser.save();

return {
id: response._id,
...response._doc,
};
} catch (error) {
console.error(error);
thrownewError('Failed to create user');
}
},
login: async (_, { name, password }) => {
try {
const user = await User.findOne({ name: name });

if (!user) {
thrownewError('User not found');
}

if (password !== user.password) {
thrownewError('Incorrect password');
}

const token = generateToken(user);

if (!token) {
thrownewError('Failed to generate token');
}

return {
message: 'Login successful',
token: token,
};
} catch (error) {
console.error(error);
thrownewError('Login failed');
}
}
},

Der registrieren Mutation übernimmt den Registrierungsprozess, indem es die neuen Benutzerdaten zur Datenbank hinzufügt. Während Anmeldung Mutation verwaltet Benutzeranmeldungen – bei erfolgreicher Authentifizierung wird ein JWT-Token generiert und in der Antwort eine Erfolgsmeldung zurückgegeben.

Fügen Sie nun den Abfragelöser zum Abrufen von Benutzerdaten hinzu. Um sicherzustellen, dass diese Abfrage nur authentifizierten und autorisierten Benutzern zugänglich ist, schließen Sie eine Autorisierungslogik ein, um den Zugriff nur auf Benutzer mit einem zu beschränken Administrator Rolle.

Im Wesentlichen prüft die Abfrage zunächst die Gültigkeit des Tokens und dann die Benutzerrolle. Wenn die Berechtigungsprüfung erfolgreich ist, fährt die Resolver-Abfrage damit fort, die Daten der Benutzer aus der Datenbank abzurufen und zurückzugeben.

 Query: {
users: async (parent, args, context) => {
try {
const token = context.req.headers.authorization || '';
const decodedToken = verifyToken(token);

if (decodedToken.role !== 'Admin') {
thrownew ('Unauthorized. Only Admins can access this data.');
}

const users = await User.find({}, { name: 1, _id: 1, role:1 });
return users;
} catch (error) {
console.error(error);
thrownewError('Failed to fetch users');
}
},
},
};

Starten Sie abschließend den Entwicklungsserver:

node server.js

Eindrucksvoll! Testen Sie nun die Funktionalität der API mithilfe der Apollo Server API-Sandbox in Ihrem Browser. Sie können zum Beispiel die verwenden registrieren Mutation, um neue Benutzerdaten in der Datenbank hinzuzufügen, und dann die Anmeldung Mutation zur Authentifizierung des Benutzers.

Fügen Sie abschließend das JWT-Token zum Autorisierungsheaderabschnitt hinzu und fahren Sie mit der Abfrage der Datenbank nach Benutzerdaten fort.

Sichern von GraphQL-APIs

Authentifizierung und Autorisierung sind entscheidende Komponenten für die Sicherung von GraphQL-APIs. Dennoch ist es wichtig zu erkennen, dass sie allein möglicherweise nicht ausreichen, um umfassende Sicherheit zu gewährleisten. Sie sollten zusätzliche Sicherheitsmaßnahmen wie Eingabevalidierung und Verschlüsselung sensibler Daten implementieren.

Durch die Einführung eines umfassenden Sicherheitsansatzes können Sie Ihre APIs vor verschiedenen potenziellen Angriffen schützen.