GraphQL har etablert seg som et populært alternativ til tradisjonelle RESTful API-arkitekturer, og tilbyr et fleksibelt og effektivt språk for dataspørringer og manipulering i APIer. Med den økende bruken er det avgjørende å prioritere sikkerheten til GraphQL APIer for å beskytte applikasjoner mot uautorisert tilgang og potensielle datainnbrudd.
En effektiv metode for å sikre GraphQL APIer er å implementere JSON Web Tokens (JWT). JWTs gir en sikker og strømlinjeformet måte å gi tilgang til beskyttede ressurser og utføre autoriserte handlinger, noe som sikrer trygg kommunikasjon mellom klienter og APIer.
Autentisering og autorisasjon i GraphQL APIer
I motsetning til REST APIer, har GraphQL APIer vanligvis et enkelt endepunkt som gir klienter muligheten til å be om ulike mengder data dynamisk. Denne fleksibiliteten, selv om den er en fordel, øker også risikoen for mulige sikkerhetstrusler, som for eksempel brutte tilgangskontroller.
For å redusere denne risikoen er det nødvendig å implementere robuste autentiserings- og autorisasjonsprosesser, inkludert grundig definisjon av tilgangstillatelser. Dette sikrer at kun godkjente brukere har tilgang til beskyttede ressurser, og dermed minimerer risikoen for potensielle sikkerhetsbrudd og datatap.
Koden for dette prosjektet er tilgjengelig på GitHub.
Konfigurer en Express.js Apollo-server
Apollo server er en populær implementasjon av en GraphQL-server for GraphQL APIer. Den gir et brukervennlig miljø for å konstruere GraphQL-skjemaer, definere resolverfunksjoner og administrere ulike datakilder for APIene dine.
For å konfigurere en Express.js Apollo Server, opprett og åpne en prosjektmappe:
mkdir graphql-API-jwt
cd graphql-API-jwt
Kjør deretter følgende kommando for å initialisere et nytt Node.js-prosjekt ved hjelp av npm, Node-pakkebehandleren:
npm init --yes
Installer nå disse nødvendige pakkene:
npm install apollo-server graphql mongoose jsonwebtokens dotenv
Til slutt, opprett en server.js-fil i rotkatalogen og konfigurer serveren din med følgende kode:
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("Koblet til DB");
return server.listen({ port: 5000 });
})
.then((res) => {
console.log(`Serveren kjører på ${res.url}`);
})
.catch(err => {
console.log(err.message);
});
GraphQL-serveren er konfigurert med `typeDefs` og `resolvers` parametere, som definerer skjemaet og operasjonene som APIet kan utføre. `Context`-alternativet konfigurerer `req`-objektet til konteksten for hver resolver, noe som gjør at serveren får tilgang til forespørselsspesifikke detaljer som header-verdier.
Opprett en MongoDB-database
For å etablere databaseforbindelsen, må du først lage en MongoDB-database eller konfigurere en klynge på MongoDB Atlas. Kopier deretter URI-strengen for databasetilkoblingen, opprett en `.env`-fil og skriv inn tilkoblingsstrengen som følger:
MONGO_URI="<mongo_connection_uri>"
Definer datamodellen
Definer en datamodell ved hjelp av Mongoose. Opprett en ny `models/user.js`-fil og legg til følgende kode:
const {model, Schema} = require('mongoose');const userSchema = new Schema({
name: String,
password: String,
role: String
});module.exports = model('user', userSchema);
Definer GraphQL-skjemaet
I et GraphQL API definerer skjemaet strukturen til dataene som kan spørres, samt gir en oversikt over tilgjengelige operasjoner (spørringer og mutasjoner) som kan brukes til å samhandle med data gjennom APIet.
For å definere et skjema, opprett en ny mappe i rotkatalogen til prosjektet og gi den navnet `graphql`. Inne i denne mappen legg til to filer: `typeDefs.js` og `resolvers.js`.
Legg til følgende kode i `typeDefs.js`-filen:
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;
Opprett resolverfunksjoner for GraphQL API
Resolverfunksjoner bestemmer hvordan data hentes som svar på klientforespørsler og mutasjoner, samt andre felt definert i skjemaet. Når en klient sender en spørring eller mutasjon, aktiverer GraphQL-serveren de tilsvarende resolverfunksjonene for å behandle og returnere de nødvendige dataene fra ulike kilder, for eksempel databaser eller APIer.
For å implementere autentisering og autorisasjon ved hjelp av JSON Web Tokens (JWTs), definer resolverfunksjoner for registrerings- og påloggingsmutasjoner. Disse vil håndtere prosessene for brukerregistrering og autentisering. Deretter opprettes en søkeløser for datahenting som kun er tilgjengelig for autentiserte og autoriserte brukere.
Men først må du definere funksjonene for å generere og verifisere JWTs. I `resolvers.js`-filen, start med å legge til følgende importer:
const User = require("../models/user");
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
Husk å legge til den hemmelige nøkkelen du skal bruke for å signere JSON Web Tokens til `.env`-filen.
SECRET_KEY = '<my_Secret_Key>';
For å generere et autentiseringstoken, legg til følgende funksjon, som også angir unike attributter for JWT-tokenet, som utløpstid. I tillegg kan du inkludere andre attributter som utstedelsestidspunkt basert på dine spesifikke applikasjonskrav.
function generateToken(user) {
const token = jwt.sign(
{ id: user.id, role: user.role },
secretKey,
{ expiresIn: '1h', algorithm: 'HS256' }
);return token;
}
Implementer deretter tokenverifiseringslogikken for å validere JWT-tokenene som er inkludert i påfølgende HTTP-forespørsler.
function verifyToken(token) {
if (!token) {
throw new Error('Token ikke oppgitt');
}try {
const decoded = jwt.verify(token, secretKey, { algorithms: ['HS256'] });
return decoded;
} catch (err) {
throw new Error('Ugyldig token');
}
}
Denne funksjonen tar et token som input, verifiserer gyldigheten ved hjelp av den spesifiserte hemmelige nøkkelen, og returnerer det dekodede tokenet hvis det er gyldig, ellers gir den en feilmelding som indikerer et ugyldig token.
Definer API-resolverne
For å definere resolverne for GraphQL API, må du skissere de spesifikke operasjonene det skal håndtere, i dette tilfellet brukerregistrerings- og påloggingsoperasjoner. Start med å lage et resolverobjekt som skal inneholde resolverfunksjonene, og definer deretter følgende mutasjonsoperasjoner:
const resolvers = {
Mutation: {
register: async (_, { userInput: { name, password, role } }) => {
if (!name || !password || !role) {
throw new Error('Navn, passord og rolle er nødvendig');
}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);
throw new Error('Kunne ikke opprette bruker');
}
},
login: async (_, { name, password }) => {
try {
const user = await User.findOne({ name: name });if (!user) {
throw new Error('Bruker ikke funnet');
}if (password !== user.password) {
throw new Error('Feil passord');
}const token = generateToken(user);
if (!token) {
throw new Error('Kunne ikke generere token');
}return {
message: 'Innlogging vellykket',
token: token,
};
} catch (error) {
console.error(error);
throw new Error('Innlogging mislyktes');
}
}
},
Registreringsmutasjonen håndterer registreringsprosessen ved å legge de nye brukerdataene til databasen. Påloggingsmutasjonen administrerer derimot brukerpålogginger – ved vellykket autentisering genereres et JWT-token, og en suksessmelding returneres i svaret.
Legg til spørringsresolveren for å hente brukerdata. For å sikre at denne spørringen kun er tilgjengelig for autentiserte og autoriserte brukere, legg til autorisasjonslogikk for å begrense tilgangen til brukere med en administratorrolle.
I hovedsak vil spørringen først kontrollere gyldigheten til tokenet og deretter brukerrollen. Hvis autorisasjonskontrollen er vellykket, fortsetter resolver-spørringen med å hente og returnere brukerdataene fra databasen.
Query: {
users: async (parent, args, context) => {
try {
const token = context.req.headers.authorization || '';
const decodedToken = verifyToken(token);if (decodedToken.role !== 'Admin') {
throw new ('Uautorisert. Kun administratorer har tilgang til disse dataene.');
}const users = await User.find({}, { name: 1, _id: 1, role:1 });
return users;
} catch (error) {
console.error(error);
throw new Error('Kunne ikke hente brukere');
}
},
},
};
Til slutt, start utviklingsserveren:
node server.js
Flott! Nå kan du teste funksjonaliteten til APIet ved å bruke Apollo Server API-sandkassen i nettleseren din. Du kan for eksempel bruke registreringsmutasjonen for å legge til nye brukerdata i databasen, og deretter bruke påloggingsmutasjonen for å autentisere brukeren.
Til slutt, legg til JWT-tokenet i autorisasjonsheaderen og fortsett med å spørre databasen etter brukerdata.
Sikring av GraphQL APIer
Autentisering og autorisasjon er essensielle komponenter for å sikre GraphQL APIer. Det er imidlertid viktig å erkjenne at de alene kanskje ikke er tilstrekkelig for å sikre omfattende sikkerhet. Det er nødvendig å implementere ytterligere sikkerhetstiltak som inputvalidering og kryptering av sensitive data.
Ved å ta i bruk en omfattende sikkerhetstilnærming kan du beskytte APIene dine mot en rekke potensielle angrep.