GraphQL è un'alternativa popolare alla tradizionale architettura API RESTful, offrendo un linguaggio di query e manipolazione dei dati flessibile ed efficiente per le API. Con i suoi crescente adozione, diventa sempre più importante dare priorità alla sicurezza delle API GraphQL per proteggere le applicazioni da accessi non autorizzati e potenziali dati violazioni.

Un approccio efficace per proteggere le API GraphQL è l'implementazione di JSON Web Token (JWT). I JWT forniscono un metodo sicuro ed efficiente per garantire l'accesso alle risorse protette ed eseguire azioni autorizzate, garantendo una comunicazione sicura tra client e API.

Autenticazione e autorizzazione nelle API GraphQL

A differenza di API REST, le API GraphQL hanno in genere un singolo endpoint che consente ai client di richiedere dinamicamente quantità variabili di dati nelle loro query. Sebbene questa flessibilità sia il suo punto di forza, aumenta anche il rischio di potenziali attacchi alla sicurezza come le vulnerabilità del controllo degli accessi interrotti.

instagram viewer

Per mitigare questo rischio, è importante implementare robusti processi di autenticazione e autorizzazione, inclusa la corretta definizione delle autorizzazioni di accesso. In questo modo, garantisci che solo gli utenti autorizzati possano accedere alle risorse protette e, in definitiva, riduci il rischio di potenziali violazioni della sicurezza e perdita di dati.

Puoi trovare il codice di questo progetto nel suo file GitHub deposito.

Configura un server Apollo Express.js

Apollo Server è un'implementazione del server GraphQL ampiamente utilizzata per le API GraphQL. Puoi usarlo per creare facilmente schemi GraphQL, definire risolutori e gestire diverse origini dati per le tue API.

Per configurare un Express.js Apollo Server, crea e apri una cartella di progetto:

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

Successivamente, esegui questo comando per inizializzare un nuovo progetto Node.js utilizzando npm, il gestore dei pacchetti Node:

npm init --yes

Ora installa questi pacchetti.

npm install apollo-server graphql mongoose jsonwebtokens dotenv

Infine, crea un file server.js file nella directory root e configura il tuo server con questo codice:

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

Il server GraphQL è configurato con typeDefs E risolutori parametri, specificando lo schema e le operazioni che l'API può gestire. IL contesto L'opzione configura l'oggetto req nel contesto di ciascun risolutore, che consentirà al server di accedere ai dettagli specifici della richiesta come i valori dell'intestazione.

Crea un database MongoDB

Per prima cosa stabilire la connessione al database creare un database MongoDB O impostare un cluster su MongoDB Atlas. Quindi, copia la stringa URI di connessione al database fornita, crea un file .env file e immettere la stringa di connessione come segue:

MONGO_URI=""

Definire il modello di dati

Definire un modello di dati utilizzando Mongoose. Creane uno nuovo modelli/utente.js file e includere il seguente codice:

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

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

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

Definire lo schema GraphQL

In un'API GraphQL, lo schema definisce la struttura dei dati che possono essere interrogati, oltre a delineare le operazioni disponibili (query e mutazioni) che è possibile eseguire per interagire con i dati tramite il API.

Per definire uno schema, crea una nuova cartella nella directory principale del tuo progetto e assegnagli un nome graphql. All'interno di questa cartella, aggiungi due file: typeDefs.js E risolutori.js.

Nel typeDefs.js file, includere il seguente codice:

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;

Crea risolutori per l'API GraphQL

Le funzioni del risolutore determinano il modo in cui i dati vengono recuperati in risposta alle query e alle mutazioni del client, nonché ad altri campi definiti nello schema. Quando un client invia una query o una mutazione, il server GraphQL attiva i risolutori corrispondenti per elaborare e restituire i dati richiesti da varie fonti, come database o API.

Per implementare l'autenticazione e l'autorizzazione utilizzando JSON Web Token (JWT), definire i risolutori per le mutazioni di registro e accesso. Questi gestiranno i processi di registrazione e autenticazione dell'utente. Quindi, crea un risolutore di query di recupero dati che sarà accessibile solo agli utenti autenticati e autorizzati.

Ma prima, definisci le funzioni per generare e verificare i JWT. Nel risolutori.js file, inizia aggiungendo le seguenti importazioni.

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

Assicurati di aggiungere la chiave segreta che utilizzerai per firmare i token Web JSON nel file .env.

SECRET_KEY = '';

Per generare un token di autenticazione, includere la seguente funzione, che specifica anche attributi univoci per il token JWT, ad esempio l'ora di scadenza. Inoltre, puoi incorporare altri attributi come "emesso in tempo" in base ai requisiti specifici dell'applicazione.

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

return token;
}

Implementare ora la logica di verifica dei token per convalidare i token JWT inclusi nelle successive richieste HTTP.

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

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

Questa funzione prenderà un token come input, ne verificherà la validità utilizzando la chiave segreta specificata e restituirà il token decodificato se è valido, altrimenti genererà un errore che indica un token non valido.

Definire gli API Resolver

Per definire i risolutori per l'API GraphQL, è necessario delineare le operazioni specifiche che gestirà, in questo caso le operazioni di registrazione e accesso dell'utente. Per prima cosa, crea un file risolutori oggetto che conterrà le funzioni di risoluzione, definirà quindi le seguenti operazioni di mutazione:

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

IL Registrati la mutazione gestisce il processo di registrazione aggiungendo i dati del nuovo utente al database. Mentre il login la mutazione gestisce gli accessi degli utenti: in caso di autenticazione riuscita, genererà un token JWT e restituirà un messaggio di successo nella risposta.

Ora includi il risolutore di query per il recupero dei dati utente. Per garantire che questa query sia accessibile solo agli utenti autenticati e autorizzati, includere la logica di autorizzazione per limitare l'accesso solo agli utenti con Ammin ruolo.

In sostanza, la query controllerà prima la validità del token e poi il ruolo dell'utente. Se il controllo di autorizzazione ha esito positivo, la query del risolutore procederà a recuperare e restituire i dati degli utenti dal database.

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

Infine, avvia il server di sviluppo:

node server.js

Eccezionale! Ora vai avanti e testa la funzionalità dell'API utilizzando la sandbox API di Apollo Server nel tuo browser. Ad esempio, puoi utilizzare il file Registrati mutazione per aggiungere nuovi dati utente nel database, quindi il file login mutazione per autenticare l'utente.

Infine, aggiungi il token JWT alla sezione dell'intestazione dell'autorizzazione e procedi a interrogare il database per i dati dell'utente.

Protezione delle API GraphQL

L'autenticazione e l'autorizzazione sono componenti cruciali per proteggere le API GraphQL. Tuttavia, è importante riconoscere che da soli potrebbero non essere sufficienti a garantire una sicurezza globale. Dovresti implementare misure di sicurezza aggiuntive come la convalida dell'input e la crittografia dei dati sensibili.

Adottando un approccio alla sicurezza completo, puoi salvaguardare le tue API da diversi potenziali attacchi.