A guide to authentication in graphql – apollo graphql blog

Introduction: what is a jwt?

For a detailed, technical description of JWTs refer to this article.

Введение

Пример разделен на 3 основных блока, в которых бизнес логика полностью соответствуют действиям пользователя.

  1. Register — регистрация нового пользователя.
  2. Authentication — аутентификация пользователя.
  3. Profile & Settings — профиль с личными данными, и настройки с редактированием и удалением данных пользователя.


Весь материал основан на предыдущих статьях с доработкой:

A guide to authentication in graphql

Note: If you’re not yet familiar with GraphQL, take a look at my other post first and come back later — you’ll get more out of it.

Update 2022: This post is from early 2022. Check out the best practices we recommend for authentication and authorization.

In this post, I want to talk about how you might go about authentication and authorization when using GraphQL. The examples I’m giving work for graphql-js, Facebook’s reference implementation, but they are quite generic and could be adapted to other implementations as well.

Before we get started, let’s make sure we’re all on the same page when it comes to the terms I’ll be using:

Authentication means checking the identity of the user making a request.

Authorization refers to the set of rules that is applied to determine what a user is allowed to see / do.

The GraphQL specification doesn’t tell you how to do authentication or authorization. That’s a good thing because it lets you use any technology you like, but it can be a bit confusing to developers who are new to GraphQL. For the rest of this post, I will write my thoughts on the topic so far and show a few options for authentication and authorization. GraphQL is still relatively new, so a best practice has yet to emerge. I don’t claim to have all the answers, so if you have a different idea or a different opinion, feel free to share that in the comments so we can all learn together!

Alright, that’s enough disclaimers for now. Let’s get started…

Unless your GraphQL API is completely public, your server will need to authenticate its users. Your options are twofold:

  1. Let the web server (e.g. express or nginx) take care of authentication.
  2. Handle authentication in GraphQL itself.

Both of these options have some advantages and some disadvantages.

If you do authentication in the web server, you can use a standard auth package (e.g. passport.js for express) and many existing authentication methods will work out of the box. You can also add and remove methods at your liking without modifying the GraphQL schema.

However, doing authentication in the web server has the downside of requiring the client to speak to an authentication endpoint in addition to the GraphQL endpoint.


Let’s start with the first option: doing authentication in the web server. What would that look like?

It’s quite straight forward. If you’re using express for example, you could use a middleware like passport.js and any authentication strategy it supports, for example username password, or OAuth. The middleware will authenticate the user or reject/redirect the request if it fails. You’ll have to provide the requisite methods for persisting users and expose the login endpoints, but I’ll spare you the details — if you need them, you can look them up in the middleware’s documentation.

Once the user is authenticated, you simply pass the user information into GraphQL as context. If you were using express, graphql-js and passport.js, that would look something like this:

import { Schema } from ‘./schema/schema.js’; //your GraphQL schema
import { graphql } from ‘graphql’;
import bodyParser from ‘body-parser’;
import express from ‘express’;
import passport from ‘passport’;
import session from ‘express-session’;
import uuid from ‘node-uuid’;require(‘./auth.js’); //see snippet belowconst app = express();
const PORT = 3000;//passport's session piggy-backs on express-session
app.use(session({
 genid: function(req) {
   return uuid.v4();
 },
 secret: ‘Z3]GJW!?9uP”/Kpe’
}));app.use(passport.initialize());
app.use(passport.session());app.post('/graphql', (req, res) => {
  graphql(schema, req.body, { user: req.user })
  .then((data) => {
    res.send(JSON.stringify(data));
  });
});//login route for passport
app.use(bodyParser.urlencoded({ extended: true }) );
app.post('/login', passport.authenticate('local', {
  successRedirect: '/',
  failureRedirect: '/login',
  failureFlash: true
}) );app.listen(PORT, () => {
 console.log(“GraphQL server listening on port %s”, PORT);
});

And in auth.js:

import passport from ‘passport’;
let LocalStrategy = require(‘passport-local’).Strategy;import {DB} from ‘./schema/db.js’;passport.use(‘local’, new LocalStrategy(
  function(username, password, done) {
    let checkPassword = DB.Users.checkPassword( username, password);    let getUser = checkPassword.then( (is_login_valid) => {
      if(is_login_valid){
        return DB.Users.getUserByUsername( username );
      } else {
        throw new Error(“invalid username or password”);
      }
    })
    .then( ( user ) => {
      return done(null, user);
    })
    .catch( (err) => {
      return done(err);
    });
  }
));passport.serializeUser(function(user, done) {
  done(null, user.id);
});passport.deserializeUser(function(id, done) {
  DB.Users.get(id).then( (user, err) => {
    return done(err, user);
  });
});

And voila, you now have access to the user object in the GraphQL resolver!


Alright, now let’s look at the second option: doing authentication in GraphQL. What would that look like?

A word of caution here: If you’re implementing authentication yourself, make sure to never store passwords in clear text or a MD5 or SHA-256 hash. Use something like bcrypt that is designed for this kind of thing. While we’re at it, make sure to not store your session tokens as-is on the server, you should hash them first.

Here, things get a little bit more complicated, because you have to do more of the heavy lifting yourself. To keep it at least reasonably simple, let’s go with usernames and passwords in this example. So an authentication flow could look like this:

First you get a session token (so your client doesn’t have to store the username and password in cleartext):

mutation {
  getSessionToken(username: 'alice', password: 'hard2remember')
}
> { getSessionToken: "6e37a03e-9ee4-42fd-912d-3f67d2d0d852" }

Then you pass this token in subsequent requests:

query {
  user(token: "6e37a03e-9ee4-42fd-912d-3f67d2d0d852"){
    todoLists{
      name
    }
  }
}> { user { todoLists: [ "Today", "Tomorrow" ] } }

When your session is over, you can call a logout mutation which invalidates the session token.

On the surface, this looks quite reasonable, but it has some downsides:

user: {
  resolve: (root, {token}){
    return { token: token, user: /*...*/ };
  }
},/* ... */todoLists: {
  resolve: (parent) => {
    let user = parent.user;
    let token = parent.token;
    /* Return lists filtered based on token ... */
  }
}

That looks bad enough, but if you need the token further down, it gets even worse. If your resolver returns a GraphQLList type, you’ll have to add the token to every item in that list.

Luckily, there’s a slightly better way in graphql-js: You can set the token on the context that every resolver receives as its third argument:

user: {
  resolve: (root, {token}, ctx){
    ctx.token = token; //assuming ctx is already an object
    return { /* user */ };
  }
}

If you are doing multiple queries, you will still have to pass the token into each one, because they get executed in parallel. In that case, writing to the context is also not a great idea any more. There is however a neat trick — or an awful hack, depending on who you ask — you can use, but it only works for mutations:

You can write a login method, which sets the context. Since mutations are executed one after the other and not in parallel, you can be sure the context is set after the login mutation:

mutation {
  loginWithToken(token: "6e37a03e-9ee4-42fd-912d-3f67d2d0d852"),
  do_stuff(greeting: "Hello", name: "Tom"),
  do_more_stuff(submarine_color: "Yellow")
}

I’ll let you in on a secret here, but please don’t shoot yourself in the foot with it: There is no difference between mutations and queries, except that mutations are executed serially, in the order given. That means the second mutation has to wait for the first to finish, the third has to wait for the second to finish, and so on. But there is nothing that prevents you from mutating state in your queries, and there is nothing that prevents you from calling a query a mutation.

That said, I would not recommend calling everything a mutation just to use the above trick. Parallel execution is an important for performance.

As you can see, this approach is not very elegant, and the workaround writes the token to the context, just like the web server would. There may be cases where you cannot modify the web server and you’re forced to do authentication in GraphQL, but in most cases I think I would prefer to handle authentication in the web server. It’s not only more generic, but also more flexible.


That’s all for now. Originally I was planning to write about authentication and authorization in the same post, but it was getting a little long so I decided to break it up. If you liked this post, make sure to also read Part 2!

I’m also working towards a complete example app to showcase all language features of GraphQL and how to use them in practice. It will include authentication and authorization as well as a bunch of other things. I‘m looking for collaborators on that, so if you’re interested, do let me know. The more, the merrier!

As always, discussion and feedback is very welcome. Just leave your comments below!

Allowed_skew​

allowed_skew is an optional field to provide some leeway (to account for clock skews) while comparing the JWT expiry
time. This field expects an integer value which will be the number of seconds of the skew value.

This is an optional field, which indicates which request header to read the JWT from. This field is an object.

Following are the possible values:

Audience​

This is an optional field. Certain providers might set a claim which indicates the intended audience for the JWT. This
can be checked by setting this field.

When this field is set, during the verification process of JWT, the aud claim in the JWT will be checked if it is
equal to the audience field given in the configuration.

See RFC for more details.

This field can be a string, or a list of strings.

Examples:

Auth0​

Refer to the Auth0 JWT Integration guide for a full integration guide with Auth0.

Auth0 publishes their JWK under:

Basics: client setup

Now it’s time to set up our GraphQL client. The idea is to get the token from the variable we set, and if it’s there, we pass it to our GraphQL client.

A guide to authentication in GraphQL - Apollo GraphQL Blog
Using the JWT in a GraphQL client

Claims_map​

This is an optional field. Certain providers might not allow adding custom claims. In such a case, you can map Hasura
session variables with existing JWT claims using claims_map. The claims_map is a JSON object where keys are session
variables and values can be a JSON path (with a default value option, when the key specified by the JSON path doesn’t
exist) or a literal value.

The literal values should be a String, except for the x-hasura-allowed-roles claim which expects a String array.

The value of a claim referred by a JSON path must be a String. To use the JSON path value, the path needs to be given
in a JSON object with path as the key and the JSON path as the value:

Example: JWT config with JSON path values

Claims_namespace_path​

An optional JSON path value to the Hasura claims in the JWT token.

Example values are $.hasura.claims or $ (i.e. root of the payload)

The JWT token should be in this format if the claims_namespace_path is set to $.hasura.claims:

{
"sub":"1234567890",
"name":"John Doe",
"admin":true,
"iat":1516239022,
"hasura":{
"claims":{
"x-hasura-allowed-roles":["editor","user","mod"],
"x-hasura-default-role":"user",
"x-hasura-user-id":"1234567890",
"x-hasura-org-id":"123",
"x-hasura-custom":"custom-value"
}
}
}

This is an optional field, with only the following possible values: – json – stringified_json

Default is json.

This is to indicate whether the Hasura specific claims are a regular JSON object or a stringified JSON.

This is required because providers like AWS Cognito only allow strings in the JWT claims.
See #1176.

Example:-

If claims_format is json then JWT claims should look like:

{
"sub":"1234567890",
"name":"John Doe",
"admin":true,
"iat":1516239022,
"https://hasura.io/jwt/claims":{
"x-hasura-allowed-roles":["editor","user","mod"],
"x-hasura-default-role":"user",
"x-hasura-user-id":"1234567890",
"x-hasura-org-id":"123",
"x-hasura-custom":"custom-value"
}
}

If claims_format is stringified_json then JWT claims should look like:

{
"sub":"1234567890",
"name":"John Doe",
"admin":true,
"iat":1516239022,
"https://hasura.io/jwt/claims":"{"x-hasura-allowed-roles":["editor","user","mod"],"x-hasura-default-role":"user","x-hasura-user-id":"1234567890","x-hasura-org-id":"123","x-hasura-custom":"custom-value"}"
}

Clerk​

Clerk integrates with Hasura GraphQL Engine using JWTs.

Code from this blogpost (finished application)

Sample code for this blogpost with an end to end working app, with SSR capabilities is available here.

Configuring jwt mode​

You can enable JWT mode by using the –jwt-secret flag or HASURA_GRAPHQL_JWT_SECRET environment variable; the value
of which is a JSON object:

{
"type":"<optional-type-of-key>",
"key":"<optional-key-as-string>",
"jwk_url":"<optional-url-to-refresh-jwks>",
"claims_namespace":"<optional-key-name-in-claims>",
"claims_namespace_path":"<optional-json-path-to-the-claims>",
"claims_format":"json|stringified_json",
"audience": <optional-string-or-list-of-strings-to-verify-audience>,
"issuer":"<optional-string-to-verify-issuer>",
"claims_map": <optional-object-of-session-variable-to-claim-jsonpath-or-literal-value>,
"allowed_skew": <optional-number-of-seconds-in-integer>,
"header":"<optional-key-to-indicate-cookie-or-authorization-header>"
}

(type, key) pair or jwk_url, one of them has to be present.

Construct a graphql schema

With the basics for the GraphQL server in order, you can now set up the actual schema that you want to use for the server. There are many approaches to define the structure of your schema, one of them being schema-first. With the schema-first approach, your schema is defined manually in the GraphQL server first, before the resolvers are connected to this schema.

As mentioned, there are multiple approaches to define the structure of your schema, and all have advantages and disadvantages. For this tutorial, the schema-first approach provides the most benefits, as the data for the schema is completely mocked, and the project is created from scratch. For a deeper understanding of these approaches, have a look at the article The Problems of “Schema-First” GraphQL Server Development by Nikolas Burk from Prisma.

The GraphQL server you’ll create in this article will show and mutate a list of events and its attendees. In the introduction of this tutorial, the type Person was already shown, which will also be used in the schema for this GraphQL server.

Before extending the schema, let’s move its definition to a separate file that you can create in the src directory. This file can be called schema.js and exports the schema for the server to use.

To create this new file, you need to run the following from the terminal:

touch src/schema.js

Afterward, copy the following code into the schema.js file:

const{ buildSchema }=require("graphql");const schema =buildSchema(`
  type Query {
    hello: String
  }
`);

module.exports = schema;

In the file src/index.js, the schema must now be imported from the file src/schema.js, which means that you can delete the import of buildSchema and add the declaration of the schema instead:

Creating a graphql server with express

With Express, you can easily create a fresh Node.js server with limited code, that is performant enough to serve as a GraphQL server. Before you can create the Express server, you’ll need to create a new project on your machine. Start by creating a new directory from your terminal by running:

mkdir auth0-graphql-server

Afterward, move into this directory and initiate a new project using npm with the following commands:

cd auth0-graphql-server
npm init -y

This last command will create the new project in the directory auth0-graphql-server and a fresh package.json file.

You can also execute the command npm init without the -y flag, which requires you to answer several questions to set up the project, such as name, author, etc.

Now that you’ve created the initial project, the packages for building the GraphQL server can be installed:

npm install express express-graphql graphql

These packages are express and graphql itself, and the express-graphql middleware that makes it possible to use your Express server with GraphQL. Also, all the dependencies of these packages are installed, as you can see from the output in your terminal.

The actual code that runs the GraphQL server must be added in a new file in this project, which you can create in a new directory called src. To create this directory and this file, you can run the commands below or use your preferred text editor or IDE.

mkdir src
touch src/index.js

And subsequently, copy and paste the following code into this file:

Customer tokens

The generateCustomerToken mutation requires the customer email address and password in the payload, as shown in the following example.

Eddsa based​

If your auth server is using EdDSA to sign JWTs, and is using the Ed25519 variant key, the JWT config only needs to have
the public key.

Example 1: public key in PEM format (not OpenSSH format):

Example 2: public key as X509 certificate:

Firebase​

This page of Firebase
docs
mentions that JWKs are published under:

Issuer​

This is an optional field. It takes a string value.

When this field is set, during the verification process of JWT, the iss claim in the JWT will be checked if it is
equal to the issuer field given in the configuration.

See RFC for more details.

Examples:

Mutating data with graphql

In this section, you’ll also make it possible to send mutations to the server that lets you change the details of an event like the title and description. Mutations need to be added to the schema, similarly to the queries. To add the mutation to your schema, you need to make the following changes to the file src/schema.js:

const{ buildSchema }=require("graphql");const schema =buildSchema(`
    type Event {
        id: ID!
        title: String!
        description: String
        date: String
        attendants: [Person!]
    }

    type Person {
        id: ID!
        name: String!
        age: Int
    }

    type Query {
        events: [Event!]!
        event(id: Int!): Event!
    }

    type Mutation {
      editEvent(id: Int!, title: String!, description: String!): Event!
    }
`);

module.exports = schema;

After adding the mutation to the schema, the resolvers also need to be able to handle this mutation by adding another resolver that can edit the data in the mock database. This mutation takes the arguments id, title, and description, and these can, therefore, also be used in the resolvers to mutate the data in the database.

...const resolvers ={events:async(_, context)=>{const{ db }=awaitcontext();return db
      .collection('events').find().toArray();},event:async({ id }, context)=>{const{ db }=awaitcontext();return db.collection('events').findOne({ id });},editEvent:async({ id, title, description }, context)=>{const{ db }=awaitcontext();return db
     .collection('events').findOneAndUpdate({ id },{ $set:{ title, description }},{ returnOriginal:false},).then(resp=> resp.value);},};...

This resolver looks at the mutation addEvent and the arguments as described in the schema, after updating the data in the mock database, the new data will be returned. You can test this mutation by using the GraphQL Playground and send the following mutation from it:

mutation{
  editEvent(id:2title:"Something else"description:"New information about this event"){
    title
    description
  }}

The output of this mutation will be the new information for the event, as the mutation is linked to the type Event:

Persisting sessions

Persisting sessions is against the OWASP security guidelines for clients and token authentication:

“… Retrieved even if the browser is restarted (Use of browser localStorage container).”

Prerequisites

Before you continue reading this tutorial, you’ll need to make sure that you’ve Node.js and npm installed on your machine. If you don’t have these installed yet, you can find the installation instructions here.

You will, of course, need to have some prior knowledge of JavaScript. Also, you will need an Auth0 authorization server to implement real-world authentication and authorization. As such, I invite you to sign up for a free Auth0 account here.

Profile & settings

Доступ к персональным данным осуществяется только после проверки public_key на сервере.

Профиль пользователя и страница настроек похожи. Они оба требуют аутентификацию пользователя. Различия в осуществляемых операциях:

  • Profile  —  GraphQL queries и subscription, или получение и обновление данных.
  • Settings  —  GraphQL mutation или редактирование и удаление данных.

Query a graphql server

As your resolvers are now connected to the mock MongoDB instance, you should be able to send queries to the GraphQL server. Instead of using the terminal to send documents to the GraphQL server, you can also install a more visual interface to help you do this.

Out-of-box express-graphql middleware offers the tool GraphiQL as a solution, but that one lacks some of the features you’ll be needing further on in this tutorial. Therefore the tool graphql-playground-middleware-express will be used instead, which you can install from npm using the command:

npm install graphql-playground-middleware-express

When the installation process has finished, this interface can be added to the setup by adding the following to the file src/index.js:

Register


При регистрации выполняется обычная GraphQL мутация, с одним нюансом. Перед сохранением, пароль

. Алгоритм шифрования SHA3 512.

Requesting and passing jwt to the graphql server

The validation function can validate JWTs that are created by Auth0 and there are a lot of ways to create tokens with Auth0. This tutorial won’t focus on how to receive or sign a token, but if you want more information on this, have a look at the Login with Auth0 document.

Rotating jwks​

Some providers rotate their JWKs (e.g. Firebase). If the provider sends

  1. no-cache, no-store or must-revalidate in Cache-Control header
  2. max-age or s-maxage in Cache-Control header
  3. or Expires header

with the response of JWK, then the GraphQL engine will refresh the JWKs automatically. If the provider does not send the
above, the JWKs are not refreshed.

Following is the behaviour in detail:

On startup:

  1. GraphQL engine will fetch the JWK and will –

    1. first, try to parse no-cache, no-store or must-revalidate in Cache-Control header.
    2. second, try to parse max-age or s-maxage directive in Cache-Control header.
    3. third, check if Expires header is present (if Cache-Control is not present), and try to parse the value as a
      timestamp.
  2. If it is able to parse max-age, s-maxage or Expires successfully, then it will use that parsed time to
    refresh/refetch the JWKs again.

    If it is unable to parse, then it will not refresh the JWKs (it assumes that if the above headers are not present,
    the provider doesn’t rotate their JWKs). If the parsed time is less than a second, the JWKs will be fetched once per
    second regardless.

    If must-revalidate and max-age are present, then it will refresh the JWK again after the time period specified
    in max-age has passed.

    However, if max-age is not specified or if no-cache or no-store are present, then it will refresh the JWKs
    once a second.

While running:

  1. While GraphQL engine is running with refreshing JWKs, in one of the refresh cycles it will –

    1. first, try to parse max-age or s-maxage directive in Cache-Control header.
    2. second, check if Expires header is present (if Cache-Control is not present), and try to parse the value as a
      timestamp.
  2. If it is able to parse any of the above successfully, then it will use that parsed time to refresh/refetch the JWKs
    again. If it is unable to parse, then it will sleep for 1 minute and will start another refresh cycle.

Rsa based​

If your auth server is using RSA to sign JWTs, and is using a 512-bit key, the JWT config only needs to have the public
key.

Example 1: public key in PEM format (not OpenSSH format):

Example 2: public key as X509 certificate:

Example 3: public key published as JWKs:

Running with jwt​

Using the flag:

Using env vars:

Setting audience check​

Certain JWT providers share JWKs between multiple tenants (like Firebase). They use the aud claim of JWT to specify
the intended tenant for the JWT. Setting the audience field in the Hasura JWT configuration will make sure that the
aud claim from the JWT is also checked during verification. Not doing this check will allow JWTs issued for other
tenants to be valid as well.

In these cases, you MUST set the audience field to appropriate value. Failing to do so is a major security
vulnerability.

Summary

Once you’ve worked through all the sections above, your app should now have all the capabilities of a modern app, using a JWT and should be secure from the common major security gotchas that JWT implementations have!

Tl;dr​

  1. The JWT must contain: x-hasura-default-role, x-hasura-allowed-roles in a custom namespace in the claims.
  2. Other optional x-hasura-* fields (required as per your defined permissions).
  3. You can send x-hasura-role as header in the request to indicate a role.
  4. Send the JWT via Authorization: Bearer <JWT> header.

Try it out!

Set up a free GraphQL backend with Hasura Cloud to try it out for yourself!

Make sure you’re on version 1.3 or above and you’re good to go.

Validating jwts on the graphql server

The prerequisites of this tutorial stated that you need an Auth0 account to complete all the sections, which you can still get by signing up for a free Auth0 account here, if you haven’t created it already.

Once you have an account, you can proceed by going to the Auth0 dashboard, where you should click on the Create API button. You’ll be redirected to a new page where you can create an API that’s able to issue and validate JSON Web Tokens (JWTs).

The form to create a new API requires the following information:

After adding this information to the form, you can click the Create button. You are then taken to the “Quick Start” section of the Auth0 API you just created. In here, click on the “Node.js” tab to get an idea of the code you’ll need to use in your GraphQL server to validate JWTs issued by Auth0.

There are two critical values on the Node.js code snippet that you’ll need in a few moments: the values of the audience and issuer properties of the object argument passed to the jwt function.

Leave this “Quick Start” page open and return to the project in your terminal where you’d need to run the following command:

npm install dotenv jsonwebtoken jwks-rsa

This command will install three packages: dotenv is used to store local environment variables, jsonwebtoken and jwks-rsa are needed to check if a JWT is valid.

First, you need to create a new file in the directory src that’s called validate.js:

touch src/validate.js

This file will hold the code to validate the JWTs that are sent to your GraphQL server. In this file, you need to add the following code:

What is graphql?

In this tutorial, you’ll build a GraphQL server with Node.js that uses Auth0 to handle authentication and authorization. But to build this server, you need to learn more about what GraphQL is and how it works. If you’re already familiar with GraphQL and its principles, you can immediately proceed to the next section of this tutorial.

GraphQL can best be described as a query language for APIs that works with the principle “Ask for what you need, get exactly that.” With GraphQL, you can send so-called documents containing operations (either queries, mutations, or subscriptions)

to a GraphQL server, and the response of the server will follow the same structure of those documents. These three types of operations all use the GraphQL query language to return the same predictable format. Also, all operations are sent to the same endpoint no matter what data you’re trying to query or mutate.

An example of such a document containing a query operation would be:

query{
  person(id:12){
    name
    age
  }}

This query is for retrieving a person with the id 12 from a GraphQL server and will return a JSON output with the same structure. For the example above, the following (mock) data will be returned:

Заключение

Это базовый пример. Его можно доработать:

  • На страницу аутентификации добавить Google reCaptcha.
  • Валидация email пользователя.
  • Установить время истекания public_key и проверка ip.
  • Мессенджер, и т.д.


Для меня это дебютная статья. На момент ее написание было 30 дней с начала моего знакомства с миром node.js.

Если у вас имеются вопросы, пожелания, предложение темы GraphQL, пишите в комментариях. Я их обязательно рассмотрю.

Спасибо за внимание.

Conclusion

In this tutorial, you’ve created a GraphQL server with Node.js, that uses a mock MongoDB instance for its data and Auth0 for authorization and authentication. You’ve learned how to set up a basic server with Express, define a GraphQL schema, and write resolvers that can collect data from a MongoDB database.

https://www.youtube.com/watch?v=2LDKGKItqgI

You can go further with learning how to handle authorization by implementing a Role-Based Access Control in your API; however, getting a token with roles is much easier if you have a working client, which I’ll teach you how to build in an upcoming tutorial.

Похожее:  Authentication For Your React and Express Application w/ JSON Web Tokens | by Faizan Virani | Medium

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *