Contents

Hur man säkrar GraphQL API:er: Implementera användarautentisering i Express.js med hjälp av JWTs

GraphQL är ett populärt alternativ till traditionell RESTful API-arkitektur och erbjuder ett flexibelt och effektivt språk för datafrågor och manipulation av API:er. Med den ökande användningen blir det allt viktigare att prioritera säkerheten för GraphQL API:er för att skydda applikationer från obehörig åtkomst och potentiella dataintrång.

En genomförbar strategi för att skydda GraphQL API:er är att använda JSON Web Tokens (JWT), som är ett effektivt sätt att ge åtkomst till begränsade resurser och utföra privilegierade åtgärder samtidigt som säker kommunikation mellan klienter och API:er upprätthålls.

Autentisering och auktorisering i GraphQL API:er

Till skillnad från RESTful Application Programming Interfaces (API:er) har GraphQL API-implementeringar ofta en enda slutpunkt som gör att klienter kan begära olika mängder och typer av information genom sina förfrågningar. Denna anpassningsförmåga är både en fördel och en potentiell källa till säkerhetsrisker, särskilt när det gäller komprometterade mekanismer för åtkomstkontroll.

/sv/images/altumcode-dc6pb2jdaqs-unsplash-1.jpg

För att effektivt kunna hantera det ovannämnda hotet är det viktigt att upprätta omfattande autentiserings- och auktoriseringsprotokoll, vilket inkluderar en noggrann avgränsning av åtkomstbehörigheter. Genom sådana åtgärder säkerställer vi att endast behöriga personer får tillgång till känsliga tillgångar, vilket minimerar sannolikheten för säkerhetsbrister och dataläckage.

[infoga länk till GitHub-arkivet här].

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 etablera en Express.js Apollo Server måste man skapa och öppna en projektkatalog.

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

För att starta ett nytt Node.js-projekt med hjälp av Node Package Manager (npm), kör följande kommando:

 npm init --yes 

Installera nu dessa paket.

 npm install apollo-server graphql mongoose jsonwebtokens dotenv 

Det är viktigt att skapa en server.js fil i den primära mappen i projektet och konfigurera servern med hjälp av det medföljande skriptet enligt följande:

 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 har konfigurerats med en kombination av typeDefs och resolvers , som definierar det schema och de operationer som API:et kan hantera.Användningen av alternativet context gör det dessutom möjligt att konfigurera objektet req så att det inkluderar den specifika kontext som är relevant för varje enskild resolver. På så sätt kan servern få tillgång till begärandespecifik information, inklusive rubrikvärden, för att underlätta effektivare datahämtning.

Skapa en MongoDB-databas

antingen skapa en ny databas i din lokala installation av MongoDB, eller använda den molnbaserade lösningen som erbjuds av MongoDB Atlas genom att konfigurera ett kluster och erhålla nödvändig anslutningsinformation. När du har fått tag på rätt URI-sträng för anslutningen kan du skapa en ny fil .env och ange anslutningsuppgifterna i det angivna formatet.

 MONGO_URI="<mongo_connection_uri>"

Definiera datamodellen

Visst, här är ett exempel på hur du definierar en datamodell för användare i en Mongoose-schemafil som heter models/user.js :javascriptconst mongoose = require(‘mongoose’);// Definiera användarschemataconst userSchema = new mongoose.Schema({name: { type: String, required: true },email: { type: String, unique: true, required: true },password: { type: String, required: true },});// Kompilera schemat till en modellconst User = mongoose.model(‘User’, userSchema);module.exports = User;I det här exemplet importerar vi först Mongoose-biblioteket högst upp i filen. Sedan skapar vi ett nytt Schema-objekt

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

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

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

Definiera GraphQL Schema

GraphQL API:s schema fastställer ordningen för information som kan hämtas, förutom att beskriva de möjliga operationer (förfrågningar och ändringar) som kan utföras för att kommunicera med data via det nämnda gränssnittet.

För att skapa ett schema för ditt projekt bör du först skapa en ny mapp i den primära katalogen för ditt företag, som kommer att kallas “graphql”. Denna speciella mapp ska fungera som en enklav där alla komponenter relaterade till GraphQL-operationer är organiserade. Inom ramen för mappen “graphql” måste det finnas två distinkta dokument - ett med namnet “typeDefs.js” och ett annat med namnet “resolvers.js”.

I filen typeDefs.js ska du infoga 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 spelar en viktig roll för att avgöra hur data erhålls som svar på klientförfrågningar om information och ändringar, inklusive eventuella ytterligare detaljer som anges i schemat.När GraphQL-servern tar emot en begäran från en klient aktiverar den relevanta resolvers för att hämta data från flera källor, inklusive databaser och externa tjänster, och returnerar dem därefter.

För att möjliggöra autentisering och åtkomstkontroll med JSON Web Token (JWT) i vår GraphQL-server måste vi först upprätta resolvers för både “register”- och “login”-mutationerna. Dessa resolvers är ansvariga för att hantera procedurerna för att skapa och verifiera konton. Därefter bör vi utveckla en resolver för data fetch query som endast kan nås av registrerade och verifierade användare som har fått tillstånd att göra det.

RSA JWE-kodare och -avkodare från paketet “node-jose”, samt “crypto”-modulen som tillhandahålls inbyggt i Node.js. Detta kommer att göra det möjligt för oss att utföra kryptografiska operationer relaterade till generering och validering av JWTs sömlöst. På så sätt kan vi säkerställa säker kommunikation mellan vår applikation och dess användare samtidigt som vi behåller enkelheten och användarvänligheten för både utvecklare och slutanvändare.

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

Se till att du inkluderar den hemliga nyckel som krävs för att signera JSON Web Tokens i din .env-fil, för att kunna använda den på rätt sätt.

 SECRET_KEY = '<my_Secret_Key>'; 

För att skapa en giltig autentiseringstoken är det viktigt att använda den medföljande funktionen och ange anpassade egenskaper, inklusive utgångstiden, inom JWT-token. Dessutom kan ytterligare attribut som utfärdandetid inkluderas baserat på de särskilda behoven i ens applikation.

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

  return token;
} 

För att säkerställa äktheten hos de JSON Web Tokens (JWT) som används inom framtida HTTP-utbyten är det nödvändigt att införliva en valideringsprocess för dessa tokens. Detta kommer att uppnås genom att implementera en autentiseringsmekanism som kontrollerar och bekräftar legitimiteten för varje JWT som presenteras under sådana interaktioner.

 function verifyToken(token) {
  if (!token) {
    throw new Error('Token not provided');
  }

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

Den givna funktionen accepterar en token som indata och använder den angivna hemliga nyckeln för att validera dess äkthet. Om token visar sig vara legitim returnerar funktionen den dekrypterade token, men vid eventuella avvikelser ger den upphov till ett undantag som anger förekomsten av en ogiltig token.

Definiera API-resolver

För att definiera resolutionsprocessen för vårt GraphQL API måste vi specificera de särskilda uppgifter eller åtgärder som den ska utföra, t.ex. hantering av användarregistrering och inloggningsprocesser. Inledningsvis skapar du ett objekt som innehåller resolverfunktioner som ska utföra de angivna uppgifterna.Implementera därefter följande mutationer för att underlätta dessa operationer:

 const resolvers = {
  Mutation: {
    register: async (_, { userInput: { name, password, role } }) => {
      if (!name || !password || !role) {
        throw new Error('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);
        throw new Error('Failed to create user');
      }
    },
    login: async (_, { name, password }) => {
      try {
         const user = await User.findOne({ name: name });

        if (!user) {
          throw new Error('User not found');
       }

        if (password !== user.password) {
          throw new Error('Incorrect password');
        }

         const token = generateToken(user);

         if (!token) {
          throw new Error('Failed to generate token');
        }

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

Registreringsmutationen ansvarar för att hantera registreringsprocessen genom att lägga till nya användardata i databasen. Å andra sidan hanterar login-mutationen användarinloggningar och vid lyckad autentisering genereras en JSON Web Token (JWT) samtidigt som ett framgångsmeddelande returneras i svaret.

För att begränsa åtkomsten till en fråga som hämtar användardata enbart till autentiserade och privilegierade användare som har rollen “Admin” är det viktigt att integrera auktoriseringslogik i systemet. Detta säkerställer att obehöriga personer inte kan få tillgång till känslig information.

I huvudsak kommer undersökningen först att validera tokenens legitimitet följt av en bedömning av användarens roll. I händelse av att auktoriseringsundersökningen visar sig vara gynnsam, ska resolutionsförfrågan hämta och ge slutanvändarnas information från arkivet.

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

        if (decodedToken.role !== 'Admin') {
          throw new ('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);
        throw new Error('Failed to fetch users');
      }
    },
  },
}; 

Slutligen startar du utvecklingsservern:

 node server.js 

Vi rekommenderar starkt att du validerar användbarheten av vårt GraphQL API genom att använda Apollo Server API sandbox i din webbläsare. För att illustrera denna process kan du använda “register”-mutationen för att mata in ny användarinformation i databasen, följt av “login”-mutationen för autentiseringsändamål.

/sv/images/login-mutation.jpg

Slutligen införlivar du JWT-token i auktoriseringshuvudparametern och hämtar därefter användarinformation från databasen genom att köra en fråga.

/sv/images/graphql-api-query.jpg

Säkra GraphQL API:er

Autentisering och auktorisering spelar en avgörande roll för säkerheten i GraphQL API:er, men att enbart implementera dem garanterar inte ett fullständigt skydd. Det är viktigt att införliva ytterligare säkerhetsåtgärder som robust inmatningsvalidering och kryptering av känslig information för att uppnå fullständig säkerhet.

Att implementera en allomfattande säkerhetsstrategi är avgörande för att skydda dina API:er (Application Programming Interface) från olika typer av hot som kan uppstå.