GraphQL är ett populärt alternativ till traditionell RESTful API-arkitektur, och erbjuder ett flexibelt och effektivt dataförfrågnings- och manipulationsspråk för API: er. Med dess växande användning blir det allt viktigare att prioritera säkerheten för GraphQL API: er för att skydda applikationer från obehörig åtkomst och potentiell data överträdelser.
En effektiv metod för att säkra GraphQL API: er är att implementera JSON Web Tokens (JWT). JWT tillhandahåller en säker och effektiv metod för att ge åtkomst till skyddade resurser och utföra auktoriserade åtgärder, vilket säkerställer säker kommunikation mellan klienter och API: er.
Autentisering och auktorisering i GraphQL API: er
Till skillnad från REST API: er, GraphQL API: er har vanligtvis en enda slutpunkt som tillåter klienter att dynamiskt begära olika mängder data i sina frågor. Även om denna flexibilitet är dess styrka, ökar den också risken för potentiella säkerhetsattacker, såsom trasiga sårbarheter i åtkomstkontroll.
För att minska denna risk är det viktigt att implementera robusta autentiserings- och auktoriseringsprocesser, inklusive korrekt definition av åtkomstbehörigheter. Genom att göra det garanterar du att endast auktoriserade användare kan komma åt skyddade resurser, och i slutändan minskar du risken för potentiella säkerhetsintrång och dataförlust.
Du kan hitta detta projekts kod i dess GitHub förvaret.
Konfigurera en Express.js Apollo-server
Apollo server är en allmänt använd GraphQL-serverimplementering för GraphQL API: er. Du kan använda den för att enkelt bygga GraphQL-scheman, definiera resolvers och hantera olika datakällor för dina API: er.
För att konfigurera en Express.js Apollo Server, skapa och öppna en projektmapp:
mkdir graphql-API-jwt
cd graphql-API-jwt
Kör sedan det här kommandot för att initiera ett nytt Node.js-projekt med hjälp av npm, Node-pakethanteraren:
npm init --yes
Installera nu dessa paket.
npm install apollo-server graphql mongoose jsonwebtokens dotenv
Till sist, skapa en server.js fil i rotkatalogen och ställ in din server med denna kod:
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);
});
GraphQL-servern är inställd med typDefs och lösare parametrar, som anger schemat och operationerna som API: et kan hantera. De sammanhang alternativet konfigurerar req-objektet till kontexten för varje resolver, vilket gör det möjligt för servern att få åtkomst till förfrågningsspecifika detaljer såsom rubrikvärden.
Skapa en MongoDB-databas
För att upprätta databasanslutningen, först skapa en MongoDB-databas eller skapa ett kluster på MongoDB Atlas. Kopiera sedan den medföljande URI-strängen för databasanslutningen, skapa en .env fil och ange anslutningssträngen enligt följande:
MONGO_URI=""
Definiera datamodellen
Definiera en datamodell med Mongoose. Skapa en ny models/user.js fil och inkludera följande kod:
const {model, Schema} = require('mongoose');
const userSchema = new Schema({
name: String,
password: String,
role: String
});
module.exports = model('user', userSchema);
Definiera GraphQL-schemat
I ett GraphQL API definierar schemat strukturen för de data som kan frågas, samt beskriver tillgängliga operationer (frågor och mutationer) som du kan utföra för att interagera med data via API.
För att definiera ett schema, skapa en ny mapp i ditt projekts rotkatalog och namnge den graphql. Lägg till två filer i den här mappen: typeDefs.js och resolvers.js.
I den typeDefs.js fil, inkludera följande kod:
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;
Skapa resolvers för GraphQL API
Resolverfunktioner bestämmer hur data hämtas som svar på klientförfrågningar och mutationer, såväl som andra fält som definieras i schemat. När en klient skickar en fråga eller mutation triggar GraphQL-servern motsvarande upplösare att bearbeta och returnera nödvändig data från olika källor, såsom databaser eller API: er.
För att implementera autentisering och auktorisering med JSON Web Tokens (JWT), definiera resolvers för registret och inloggningsmutationer. Dessa kommer att hantera processerna för användarregistrering och autentisering. Skapa sedan en datahämtningsfrågelösare som endast är tillgänglig för autentiserade och auktoriserade användare.
Men först, definiera funktionerna för att generera och verifiera JWT. I den resolvers.js fil, börja med att lägga till följande importer.
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
Se till att lägga till den hemliga nyckel som du använder för att signera JSON-webbtokens till .env-filen.
SECRET_KEY = '' ;
För att generera en autentiseringstoken, inkludera följande funktion, som också anger unika attribut för JWT-token, t.ex. utgångstiden. Dessutom kan du införliva andra attribut som utfärdats vid tidpunkten baserat på dina specifika applikationskrav.
functiongenerateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
);
return token;
}
Implementera nu tokenverifieringslogiken för att validera JWT-tokens som ingår i efterföljande HTTP-förfrågningar.
functionverifyToken(token) {
if (!token) {
thrownewError('Token not provided');
}
try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
thrownewError('Invalid token');
}
}
Den här funktionen tar en token som indata, verifierar dess giltighet med den angivna hemliga nyckeln och returnerar den avkodade token om den är giltig, annars ger ett felmeddelande som indikerar en ogiltig token.
Definiera API-upplösare
För att definiera resolvers för GraphQL API måste du beskriva de specifika operationerna som den kommer att hantera, i det här fallet användarregistrering och inloggningsoperationer. Skapa först en lösare objekt som kommer att hålla resolverfunktionerna, definierar sedan följande mutationsoperationer:
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');
}
}
},
De Registrera mutation hanterar registreringsprocessen genom att lägga till nya användardata till databasen. Medan logga in mutation hanterar användarinloggningar – vid framgångsrik autentisering kommer den att generera en JWT-token, samt returnera ett framgångsmeddelande i svaret.
Inkludera nu frågelösaren för att hämta användardata. För att säkerställa att denna fråga endast kommer att vara tillgänglig för autentiserade och auktoriserade användare, inkludera auktoriseringslogik för att begränsa åtkomsten till endast användare med en Administration roll.
I huvudsak kommer frågan först att kontrollera tokens giltighet och sedan användarrollen. Om auktoriseringskontrollen lyckas fortsätter resolverfrågan att hämta och returnera användarnas data från databasen.
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');
}
},
},
};
Starta slutligen utvecklingsservern:
node server.js
Grymt bra! Nu, fortsätt och testa funktionaliteten hos API: t med hjälp av Apollo Server API-sandlådan i din webbläsare. Du kan till exempel använda Registrera mutation för att lägga till nya användardata i databasen, och sedan logga in mutation för att autentisera användaren.
Lägg slutligen till JWT-token till behörighetsrubriksektionen och fortsätt med att fråga databasen efter användardata.
Säkra GraphQL API: er
Autentisering och auktorisering är avgörande komponenter för att säkra GraphQL API: er. Icke desto mindre är det viktigt att inse att de ensamma kanske inte är tillräckliga för att garantera en omfattande säkerhet. Du bör implementera ytterligare säkerhetsåtgärder som indatavalidering och kryptering av känslig data.
Genom att anta en omfattande säkerhetsstrategi kan du skydda dina API: er mot olika potentiella attacker.