Thibault Mocellin

Thibault Mocellin

Développeur Full-Stack freelance basé à Annecy 🇫🇷

Authentification et Autorisation GraphQL/Parse Server

Posted on June 26, 2017

Dans un article précédent nous avons vu comment utiliser GraphQL avec Parse Server. Maintenant nous allons voir comment restreindre l’accès aux utilisateurs non authentifiés et comment limiter les résultats des requêtes aux données auxquelles ils ont accès.

Notre exemple sera le suivant l’api permettra de créer des utilisateurs, une fois authentifiés ils pourront poster des messages, récupérer l’ensemble de leurs messages.

Commençons par ajouter nos types d’objets dans notre schema :

/********* Object Types *********/

const userType = new GraphQLObjectType({
  name: 'User',
  description: 'A simple user',
  fields: () => ({
    id: {
      type: GraphQLID,
      resolve: (obj) => obj.id,
    },
    username: {
      type: GraphQLString,
      resolve: (obj) => obj.get('username'),
    },
    sessionToken: {
      type: GraphQLString,
      resolve: (obj) => obj.getSessionToken(),
    },
  }),
});

const postType = new GraphQLObjectType({
  name: 'Post',
  description: 'A simple post message',
  fields: () => ({
    id: {
      type: GraphQLID,
      resolve: (obj) => obj.id,
    },
    message: {
      type: GraphQLString,
      resolve: (obj) => obj.get('message'),
    },
    author: {
      type: userType,
      resolve: (obj) => obj.get('author'),
    },
  }),
});

Nous avons nos deux types d’objet User et Post. Précision pour le type User le champ sessionToken n’est accessible que lorsqu’on retourne l’utilisateur après la création ou la connexion, ensuite ce champs n’est plus accessible sa valeur sera toujours null. Nous devrons le stocké coté client pour pouvoir authentifier nos requêtes. Le champ author du type Post est un pointer vers le type User.

Création et connexion des utilisateurs

Avant de passer à l’authentification des requêtes nous allons d’abord créer deux mutations qui vont nous permettre de créer un nouvel utilisateur et lui permettre de se connecter.

/********* Mutation Types *********/
const signUp = {
  type: userType,
  description: 'Create a new user',
  args: {
    username: {
      type: GraphQLString,
    },
    email: {
      type: GraphQLString,
    },
    password: {
      type: GraphQLString,
    },
  },
  resolve: (value, { username, email, password }) => {
    var user = new Parse.User();
    user.set('username', username);
    user.set('password', password);
    user.set('email', email);
    return user.signUp();
  },
};

const login = {
  type: userType,
  description: 'Connects the user',
  args: {
    username: {
      type: GraphQLString,
    },
    password: {
      type: GraphQLString,
    },
  },
  resolve: (value, { username, password }) => {
    var user = new Parse.User();
    user.set('username', username);
    user.set('password', password);
    return Parse.User.logIn(username, password);
  },
};

Authentification

Pour authentifier les utilisateurs nous allons nous servir du sessionToken retourné lors de la création ou de la connexion.

Nous allons avoir deux manières différentes de récupérer ce token :

  • Par requête: Dans ce cas la nous passons le sessionToken en tant qu’argument de la requête
  • Par contexte : Dans ce cas nous ajoutons le token dans le header de la requête HHTP et ensuite on le récupère dans nos resolver par le context de graphQL.

Exemple récupération par requête

const getPosts = {
  type: new GraphQLList(postType),
  args: {
    sessionToken: {
      type: GraphQLString,
    },
  },
  description: 'list of posts',
  resolve: (value, { sessionToken }, context) => {
    /* validate sessionToken */
    /* make query */
  },
};

Exemple récupération par contexte

La première chose à faire dans ce cas est de récupérer la valeur du token dans le header. On effectue cela lorsqu’on définit le serveur graphQL :

//GraphQL
app.use(
  '/graphql',
  GraphQLHTTP((request) => {
    return {
      graphiql: true,
      pretty: true,
      schema: schema,
      context: { sessionToken: request.headers['x-parse-session-token'] },
    };
  })
);

Puis on récupère le sessionToken dans la requête de cette manière :

const getPosts = {
  type: new GraphQLList(postType),
  args: {
    sessionToken: {
      type: GraphQLString,
    },
  },
  description: 'list of posts',
  resolve: (value, args, { sessionToken }) => {
    /* validate sessionToken */
    /* make query */
  },
};

Astuce : Si vous testez ce système avec GraphiQL vous ne pouvez pas ajouter le sessionToken dans le header de la requête. La solution est d’utiliser cette extension chrome qui vous permettra d’ajouter des paramètres dans le header de vos requêtes.

Restriction d’accès

Maintenant nous allons voir comment nous pouvons limiter une requête seulement aux utilisateurs connectés.

Pour cela nous allons ajouter une fonction nommée isAuthorized :

const isAuthorized = async (token) => {
  const q = new Parse.Query(Parse.Session)
    .include('user')
    .equalTo('sessionToken', token);
  const session = await q.first({ useMasterKey: true });
  if (typeof session === 'undefined') {
    throw new Error('Unauthorized');
  }
  return session;
};

Pour déterminer si l’utilisateur est autorisé à exécuter la requête on passe à la fonction le sessionToken. Ensuite on recherche (Parse.Query) une session qui a pour sessionToken le token passé en paramètre. Etant donnée qu’une session Parse possède un pointer vers l’utilisateur de la session on inclut dans la requête la récupération des données de l’utilisateur. Enfin on test si une session a été trouvée si oui on retourne la session sinon on lève une erreur.

Il ne reste plus qu’à ajouter cette fonction dans chaque resolver des requêtes ou mutations que l’on souhaite restreindre.

Restriction des données

Pour terminer cet article nous allons rajouter une contrainte sur la récupération des données.

D’abord nous allons ajouter une mutation qui permettra d’ajouter des messages.

const createPost = {
  type: postType,
  description: 'add new post',
  args: {
    message: {
      type: GraphQLString,
    },
  },
  resolve: async (value, { message }, { sessionToken }) => {
    const session = await isAuthorized(sessionToken);
    var Post = Parse.Object.extend('Post');
    var post = new Post();
    post.set('message', message);
    post.set('author', session.get('user'));
    post.setACL(new Parse.ACL(session.get('user')));
    return post.save();
  },
};

La mutation possède un argument qui est la valeur du message, ensuite on restreint l’accès à cette mutation aux utilisateurs authentifiés avec la fonction isAuthorized. Enfin on crée le nouveau post et restreint l’accès en lecture et écriture à l’utilisateur qui crée le post à l’aide de ACL.

Maintenant on vient modifier la requête qui retourne les posts pour qu’elle ne retourne que les posts que l’utilisateur a créé.

const getPosts = {
  type: new GraphQLList(postType),
  description: 'list of posts',
  resolve: async (value, args, { sessionToken }) => {
    const session = await isAuthorized(sessionToken);
    var Post = Parse.Object.extend('Post');
    return new Parse.Query(Post).include('author').find({ sessionToken });
  },
};

Ici premièrement on restreint l’accès aux utilisateurs connectés puis on effectue la requête de récupération des posts. Pour limiter les résultats il nous suffit de passer le sessionToken dans la méthode find et Parse s’occupera de retourner que les posts d’ont l’utilisateur à accès.

Et voila nous en avons fini avec l’authentification et l’autorisation avec GraphQL et Parse Server.Vous pouvez retrouver l’ensemble du code source ici.