[reprint] using JSON web token

Time:2021-6-23

After such a long time of web development, from JSF, spring, hibernate framework in Java EE, to spring web MVC, to ThinkPHP framework in PHP, to nodejs now, my own view is that I like the clean and tidy web layer more and more. When I used JSF to develop the view layer, I used primefaces to display the interface, although primefaces did provide great convenience, Developers can focus on business logic development, which in fact saves the work of front-end development. But later found that some customers need to show the form is difficult to achieve, or through the patchwork method to achieve the results of low efficiency. Use inflexible, later oneself gradually turned to do front-end engineer. Spring web MVC can make the web layer clean and tidy, separate the web layer, and communicate with the server through Ajax. Now when learning the angularjs framework, the background data server intends to use the rest style interface to do this. In the front-end and back-end interaction, we need to consider the security of data communication. This is mentioned in the understanding of session.

Please indicate the source of Reprint:http://www.haomou.net/2014/08/13/2014_web_token/

sequence of events

Front end framework libraries such as ember, angular and backbone are growing with more sophisticated web applications. Because of this, the construction of the server side is also getting rid of the traditional task and becoming more like API. API decouples traditional concepts of front end and back end. Developers can separate from the front end, develop the back end independently, and get more convenience in testing. This approach also enables a mobile application and a web application to use the same back end.

When using an API, one of the challenges is authentication. In traditional web applications, the server successfully returns a response depends on two things. First, it saves session information through a storage mechanism. Each session has its own unique information (ID), often a long, randomized string, which is used for future requests to retrieve information. Secondly, the information contained in the response header enables the client to save a cookie. The server automatically adds the session ID to each sub request, which enables the server to identify the user by retrieving the information in the session. This is how traditional web applications get around the fact that HTTP is status.

The API should be designed to be stateless. This means that there are no login and logout methods, no sessions, and the API designers can’t rely on cookies, because they can’t guarantee that these requests are issued by the browser. Naturally, we need a new mechanism. This article focuses on JSON web tokens, abbreviated as jwts, a possible mechanism to solve this problem. This article uses node’s express framework as the back end and backbone as the front end.

common method

The first one is to use the basic auth specified in the HTTP specification. It needs to set an authentication header in the response. Clients must attach their credentials to each subresponse, including its password. If these credentials pass, then the user’s information will be delivered to the server application.

The second aspect is similar, but uses the application’s own verification mechanism. It usually includes checking the sent certificate with the stored certificate. Compared with basic auth, this method needs to send credentials in each call.

The third is OAuth (or oauth2). Designed for third party authentication, but more difficult to configure. At least on the server side.

In use, users are not allowed to submit their user name and password every time. Usually, the client exchanges some reliable information with the server to get the token, which is used as the permission key for the customer service to request again. Token is usually longer and more complex than password. For example, jwts are usually 150 characters long. Once you get the token, you need to attach it every time you call the API. Then, it’s still more secure than sending accounts and passwords directly, even if it’s HTTPS.
Think of token as a safe passport. You verify your identity (through your user name and password) in a secure front desk. If you successfully verify yourself, you can get this. When you walk into the building (trying to get resources from calling the API), you will be asked to verify your passport instead of re verifying it at the front desk.

JWTs

Jwts is a draft, although in essence it is a more specific authentication and authorization mechanism. A JWT is divided into three parts by period. JWT is URL safe, which means it can be used to query character parameters( Translator’s note: that is, it can be separated from the URL, regardless of the URL information). About the JSON web token, seehttp://self-issued.info/docs/draft-ietf-oauth-json-web-token.html

The first part of JWT is the encoded string of a simple JS object, which is used to describe the token type and the hash algorithm used. The following example shows a JWT token using HMAC SHA-256 algorithm.

{
  "typ" : "JWT",
  "alg" : "HS256"
}

After encryption, the object becomes a string:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9

The second part of JWT is the core of token, which also encodes a JS object and contains some summary information. Some are necessary, some are selective. Examples are as follows:

{
  "iss": "joe",
  "exp": 1300819380,
  "http://example.com/is_root": true
}

This structure is called JWT claims set. This ISS is the abbreviation of the issuer, indicating that the entity of the request can be the information of the user who made the request. Exp, short for expires, is used to specify the life cycle of the token( Refer to the document for relevant parameters

eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

The third part of JWT is the signature of JWT according to the first part and the second part. Like this:

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Finally, combine the above, and the JWT token is as follows:

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Handling tokens

We will use JWT simple module to deal with token, which will free us from studying how to encrypt and decrypt. If you are interested, you can read this note, or read the source code of this warehouse.
First, we will install the library using the following command. Remember that you can add – save to the command to automatically add it to your package. JSON file.

npm install jwt-simple
In the beginning of your application, add the following code. This code introduces express and JWT simple, and creates a new express application. The last line sets a variable named jwttokensecret of the app, whose value is “you”_ SECRET_ Remember to change it to something else.

var express = require('express');
var jwt = require('jwt-simple');
var app = express();

app.set('jwtTokenSecret', 'YOUR_SECRET_STRING');

Get token

The first thing we need to do is let the client exchange tokens through their account and password. There are two possible methods in the restful API. The first is to use the post request to pass the verification and make the server send the response with token. In addition, you can use get requests, which require them to provide credentials (URL) with parameters, or better use request headers.
The purpose of this article is to explain the token authentication method rather than the basic user name / password authentication mechanism. So let’s assume that we have obtained the user name and password through the request:

User.findOne({ username: username }, function(err, user) {
  if (err) {
    // user not found
    return res.send(401);
  }

  if (!user) {
    // incorrect username
    return res.send(401);
  }

  if (!user.validPassword(password)) {
    // incorrect password
    return res.send(401);
  }

  // User has authenticated OK
  res.send(200);
});
If the user successfully verifies the account and password, then we generate a token and return it to the user.

1
var expires = moment().add('days', 7).valueOf();
var token = jwt.encode({
  iss: user.id,
  exp: expires
}, app.get('jwtTokenSecret'));

res.json({
  token : token,
  expires: expires,
  user: user.toJSON()
});

Notice that the JWT. Encode() function has two arguments. The first is an object that needs to be encrypted, and the second is an encrypted key. This token is composed of ISS and exp that we mentioned earlier. Note that moment. JS is used to set the token to be invalid after 7 days. The res.json () method is used to pass the JSON object to the client.

Verify token

After the client obtains the token, it should attach the token to the server every time it requests data, and then the server verifies the token.
In order to verify JWT, we need to write some middleware that can complete these functions

Check the attached token
Trying to decrypt
Verify the availability of token
If the token is legal, retrieve the user’s information and attach it to the requested object
Let’s write a middleware framework

// @file jwtauth.js

var UserModel = require('../models/user');
var jwt = require('jwt-simple');

module.exports = function(req, res, next) {
  // code goes here
};

For maximum scalability, we allow the client to use the following three methods to attach our token: as the parameter of the request link, as the parameter of the body, and as the parameter of the request header. For the last one, we’ll use header x-access-token.

The following is our code allowed in the middleware, trying to retrieve the token:

var token = (req.body && req.body.access_token) || (req.query && req.query.access_token) || req.headers['x-access-token'];

Note that in order to access req. Body, we need to first use the express. Bodyparser () middleware.
Next, let’s analyze JWT

if (token) {
  try {
    var decoded = jwt.decode(token, app.get('jwtTokenSecret'));

    // handle token here

  } catch (err) {
    return next();
  }
} else {
  next();
}

If the parsing process fails, the JWT simple component will throw an exception. If an exception occurs or there is no token, we will call next () to continue processing the request. This means that we can’t identify users. If a qualified token is legal and decoded, we should get two attributes: ISS contains the user ID and exp contains the token expiration timestamp. We will deal with the latter first, and if it is out of date, we will reject it:

if (decoded.exp <= Date.now()) {
  res.end('Access token has expired', 400);
}

If the token is still legal, we can retrieve the user information and attach it to the request object

User.findOne({ _id: decoded.iss }, function(err, user) {
  req.user = user;
});

Finally, the middleware is attached to the route

var jwtauth = require('./jwtauth.js');

app.get('/something', [express.bodyParser(), jwtauth], function(req, res){
  // do something
});

Or match some routes

app.all('/api/*', [express.bodyParser(), jwtauth]);

client request

We provide a simple get side to get a remote token. This is very direct, so we don’t have to worry about the details. We just launch a request, pass the user name and password. If the request succeeds, we will get a response containing the token.

We’re looking at subsequent requests. One way is through the ajaxsetup () method of jQuery. This can be used directly for Ajax requests, or wrapped Ajax methods can be used through the front-end framework. For example, suppose we use our requestwindow.localStorage.setItem(‘token’, ‘the-long-access-token’);In the local storage, we can attach the token to the request header in this way

var token = window.localStorage.getItem('token');

if (token) {
  $.ajaxSetup({
    headers: {
      'x-access-token': token
    }
  });
}

It’s simple, but it hijacks all Ajax requests if there is a token in the local storage. It will be attached to a header called x-access-token.

bear token

For the bear token, please refer to RFC 6750: the OAuth 2.0 authorization framework: Bear token usage. At present, major domestic websites use different tokens, and it is not said that bear token must be used. Only twitter explicitly states that bear token is used.

OAuth 2.0 (RFC 6749) defines how the client obtains the access token. The client can use the access token to obtain the protected resource from the resource server in the name of resource owner. For example, I (resource owner) authorize a mobile app (client) to obtain my protected resource from the resource server in the name of resource owner. OAuth 2.0 defines that access token is the only way for resource server to authenticate. With this, resource server does not need to provide other authentication methods, such as account and password.

However, only abstract concepts are defined in RFC 6749, such as the format of access token, how to transfer to resource server, and how to deal with resource server when access token is invalid. Therefore, the usage of bearer token is defined in RFC 6750. Bearer token is a kind of access token, which is approved and issued to the client by the authorization server with the permission of the resource owner. As long as the resource server recognizes the token, it can confirm that the client has obtained the permission of the resource owner. There is no need to verify the authenticity of the token in the way of cryptography. There is another article about the security of the stolen token.

The format of bearer token

Bearer XXXXXXXX

The format of XXXXXXXX is b64token, and the definition of ABNF is as follows:

b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / "/" ) *"="

Write it as regular expression

/[A-Za-z0-9\-\._~\+\/]+=*/

About the bear token, I plan to start another article, which details: Bear token

Express JWT instance

The following is a specific example. The client of this example is web app, which uses angularjs framework. The server uses the restful API interface made by nodejs, and the client calls the interface data directly, in which the token authentication mechanism is used.
When the user sends his authorization information, the node.js service checks whether it is correct, and then returns a unique token based on the user’s information. The angularjs application saves the token in the user’s session storage, and then adds the authorization containing the token in the request header when sending the request. If the endpoint needs to confirm the user’s authorization, the server checks and verifies the token, and then returns data if it succeeds. If it fails, 401 or other exceptions are returned.
Technology used:

AngularJS
Nodejs (express. JS, express JWT and moongoose)
MongoDB
Redis (standby, used to record the token that has not timed out when the user logs out)
Client: angularjs part

First, let’s create our adminuserctrl controller and handle the login / logout action.

appControllers.controller('AdminUserCtrl', ['$scope', '$location', '$window', 'UserService', 'AuthenticationService',
    function AdminUserCtrl($scope, $location, $window, UserService, AuthenticationService) {
 
        //Admin User Controller (login, logout)
        $scope.logIn = function logIn(username, password) {
            if (username !== undefined && password !== undefined) {
 
                UserService.logIn(username, password).success(function(data) {
                    AuthenticationService.isLogged = true;
                    $window.sessionStorage.token = data.token;
                    $location.path("/admin");
                }).error(function(status, data) {
                    console.log(status);
                    console.log(data);
                });
            }
        }
 
        $scope.logout = function logout() {
            if (AuthenticationService.isLogged) {
                AuthenticationService.isLogged = false;
                delete $window.sessionStorage.token;
                $location.path("/");
            }
        }
    }
]);

The controller uses two services: userservice and authenticationservice. The first process calls the rest API with a certificate. The latter deals with user authentication. It has only one Boolean value to indicate whether the user is authorized or not.

appServices.factory('AuthenticationService', function() {
    var auth = {
        isLogged: false
    }
 
    return auth;
});
appServices.factory('UserService', function($http) {
    return {
        logIn: function(username, password) {
            return $http.post(options.api.base_url + '/login', {username: username, password: password});
        },
 
        logOut: function() {
 
        }
    }
});

OK, we need to make a landing page:

<form class="form-horizontal" role="form">
    <div class="form-group">
        <label for="inputUsername" class="col-sm-4 control-label">Username</label>
        <div class="col-sm-4">
            <input type="text" class="form-control" id="inputUsername" placeholder="Username" ng-model="login.email">
        </div>
    </div>
    <div class="form-group">
        <label for="inputPassword" class="col-sm-4 control-label">Password</label>
        <div class="col-sm-4">
            <input type="password" class="form-control" id="inputPassword" placeholder="Password" ng-model="login.password">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-4 col-sm-10">
            <button type="submit" class="btn btn-default" ng-click="logIn(login.email, login.password)">Log In</button>
        </div>
    </div>
</form>

When the user sends his information, our controller sends the content to the node.js server. If the information is available, we set islogged in the authentication service to true. We save the token sent from the server for the next request. When it comes to node.js, we will see how to deal with it.

OK, we need to add a special header to each request: [authorization: bearer]. To achieve this requirement, we set up a service called token interceptor.

appServices.factory('TokenInterceptor', function ($q, $window, AuthenticationService) {
    return {
        request: function (config) {
            config.headers = config.headers || {};
            if ($window.sessionStorage.token) {
                config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
            }
            return config;
        },
 
        response: function (response) {
            return response || $q.when(response);
        }
    };
});

Then we append the interceptor to $httpprovider:

app.config(function ($httpProvider) {
    $httpProvider.interceptors.push('TokenInterceptor');
});

Then, we need to start to configure the route to let angularjs know which needs authorization. Here, we need to check whether the user has been authorized, that is, to check the islogged value of the authentication service.

app.config(['$locationProvider', '$routeProvider', 
  function($location, $routeProvider) {
    $routeProvider.
        when('/', {
            templateUrl: 'partials/post.list.html',
            controller: 'PostListCtrl'
        }).
        when('/post/:id', {
            templateUrl: 'partials/post.view.html',
            controller: 'PostViewCtrl'
        }).
        when('/tag/:tagName', {
            templateUrl: 'partials/post.list.html',
            controller: 'PostListTagCtrl'
        }).
        when('/admin', {
            templateUrl: 'partials/admin.post.list.html',
            controller: 'AdminPostListCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/post/create', {
            templateUrl: 'partials/admin.post.create.html',
            controller: 'AdminPostCreateCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/post/edit/:id', {
            templateUrl: 'partials/admin.post.edit.html',
            controller: 'AdminPostEditCtrl',
            access: { requiredLogin: true }
        }).
        when('/admin/login', {
            templateUrl: 'partials/admin.login.html',
            controller: 'AdminUserCtrl'
        }).
        when('/admin/logout', {
            templateUrl: 'partials/admin.logout.html',
            controller: 'AdminUserCtrl',
            access: { requiredLogin: true }
        }).
        otherwise({
            redirectTo: '/'
        });
}]);

app.run(function($rootScope, $location, $window, AuthenticationService) {
    $rootScope.$on("$routeChangeStart", function(event, nextRoute, currentRoute) {
        //redirect only if both isLogged is false and no token is set
        if (nextRoute != null && nextRoute.access != null && nextRoute.access.requiredLogin 
            && !AuthenticationService.isLogged && !$window.sessionStorage.token) {

            $location.path("/admin/login");
        }
    });
});

Server: node.js + mongodb

In order to process authorization information in our restful API, we need to use express JWT (JSON web token) to generate a unique token based on user information. And verify the token.

First, we create a user schema in mongodb. We also need to create and call a middleware to encrypt the password before creating and saving the user information to the database. In addition, we need a method to decrypt the password. When receiving the user’s request, check whether there is a match in the database.

var Schema = mongoose.Schema;
 
// User schema
var User = new Schema({
    username: { type: String, required: true, unique: true },
    password: { type: String, required: true}
});
 
// Bcrypt middleware on UserSchema
User.pre('save', function(next) {
  var user = this;
 
  if (!user.isModified('password')) return next();
 
  bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
    if (err) return next(err);
 
    bcrypt.hash(user.password, salt, function(err, hash) {
        if (err) return next(err);
        user.password = hash;
        next();
    });
  });
});
 
//Password verification
User.methods.comparePassword = function(password, cb) {
    bcrypt.compare(password, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(isMatch);
    });
};

Then we start to write the method of authorized user and token creation

exports.login = function(req, res) {
    var username = req.body.username || '';
    var password = req.body.password || '';
 
    if (username == '' || password == '') {
        return res.send(401);
    }
 
    db.userModel.findOne({username: username}, function (err, user) {
        if (err) {
            console.log(err);
            return res.send(401);
        }
 
        user.comparePassword(password, function(isMatch) {
            if (!isMatch) {
                console.log("Attempt failed to login with " + user.username);
                return res.send(401);
            }
 
            var token = jwt.sign(user, secret.secretToken, { expiresInMinutes: 60 });
 
            return res.json({token:token});
        });
 
    });
};

Finally, we need to add JWT middleware to all the routes that need to be authorized for access

/*
Get all published posts
*/
app.get('/post', routes.posts.list);
/*
    Get all posts
*/
app.get('/post/all', jwt({secret: secret.secretToken}), routes.posts.listAll);
 
/*
    Get an existing post. Require url
*/
app.get('/post/:id', routes.posts.read);
 
/*
    Get posts by tag
*/
app.get('/tag/:tagName', routes.posts.listByTag);
 
/*
    Login
*/
app.post('/login', routes.users.login);
 
/*
    Logout
*/
app.get('/logout', routes.users.logout);
 
/*
    Create a new post. Require data
*/
app.post('/post', jwt({secret: secret.secretToken}), routes.posts.create);
 
/*
    Update an existing post. Require id
*/
app.put('/post', jwt({secret: secret.secretToken}), routes.posts.update);
 
/*
    Delete an existing post. Require id
*/
app.delete('/post/:id', jwt({secret: secret.secretToken}), routes.posts.delete);

The above example uses token to verify the API interface, but there are two problems to be solved

The user logs out, but the token is not invalid because the server does not delete the token
If the token is invalid, what should I do? If I still let the user log in to get the token again, I will have a bad experience. There should be a token refresh mechanism.
Using redis to solve problem 1

The solution is: when the user clicks the logout button, the token will only be saved for a period of time, that is, the period when the token is valid after you log in with JSON webtoken. We store the token in redis, and the lifetime is also the time when JWT gets the token. After this time, the token will be automatically deleted by redis. Finally, we create a nodejs middleware to check whether all the tokens used by restricted endopoint exist in the redis database.
Nodejs configuring reids

var redis = require('redis');
var redisClient = redis.createClient(6379);
 
redisClient.on('error', function (err) {
    console.log('Error ' + err);
});
 
redisClient.on('connect', function () {
    console.log('Redis is ready');
});
 
exports.redis = redis;
exports.redisClient = redisClient;
Then, let's create a method to check whether the token provided is used

Token management and Middleware

In order to save the token in redis, we need to create a method to get the token parameter of the header in the request, and then save it as the key of redis. We don't care what the value is.

var redisClient = require('./redis_database').redisClient;
var TOKEN_EXPIRATION = 60;
var TOKEN_EXPIRATION_SEC = TOKEN_EXPIRATION * 60;
 
exports.expireToken = function(headers) {
    var token = getToken(headers);
 
    if (token != null) {
        redisClient.set(token, { is_expired: true });
        redisClient.expire(token, TOKEN_EXPIRATION_SEC);
    }
};
 
var getToken = function(headers) {
    if (headers && headers.authorization) {
        var authorization = headers.authorization;
        var part = authorization.split(' ');
 
        if (part.length == 2) {
            var token = part[1];
 
            return part[1];
        }
        else {
            return null;
        }
    }
    else {
        return null;
    }
};

Then, create a middleware to verify the token

// Middleware for token verification
exports.verifyToken = function (req, res, next) {
    var token = getToken(req.headers);
 
    redisClient.get(token, function (err, reply) {
        if (err) {
            console.log(err);
            return res.send(500);
        }
 
        if (reply) {
            res.send(401);
        }
        else {
            next();
        }
 
    });
};

Verifytoken is a middleware used to get the token in the request header and search it in redis. If the token is found, we will send HTTP 401. Otherwise, we will continue the workflow and let the request access the API.

We need to execute the expiratoken method when the user clicks logout

exports.logout = function(req, res) {
    if (req.user) {
        tokenManager.expireToken(req.headers);
 
        delete req.user;
        return res.send(200);
    }
    else {
        return res.send(401);
    }
}

Finally, we update the routing and use the new middleware

//Login
app.post('/user/signin', routes.users.signin);
 
//Logout
app.get('/user/logout', jwt({secret: secret.secretToken}), routes.users.logout);
 
//Get all posts
app.get('/post/all', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.listAll);
 
//Create a new post
app.post('/post', jwt({secret: secret.secretToken}), tokenManager.verifyToken , routes.posts.create);
 
//Edit the post id
app.put('/post', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.update);
 
//Delete the post id
app.delete('/post/:id', jwt({secret: secret.secretToken}), tokenManager.verifyToken, routes.posts.delete);

OK, now every time we send a request, we will parse the token and see if it is valid.
Here is the source code of the whole project

Refresh token to solve problem 2

 appServices.factory('TokenInterceptor', function ($q, $window, $location,    AuthenticationService) {
        return {
            request: function (config) {
                config.headers = config.headers || {};
                if ($window.sessionStorage.token) {
                    config.headers.Authorization = 'Bearer ' +     $window.sessionStorage.token;
                }
                return config;
            },

            requestError: function(rejection) {
                return $q.reject(rejection);
            },

            /* Set Authentication.isAuthenticated to true if 200 received */
            response: function (response) {
                if (response != null && response.status == 200 && $window.sessionStorage.token && !AuthenticationService.isAuthenticated) {
                    AuthenticationService.isAuthenticated = true;
                }
                return response || $q.when(response);
            },

            /* Revoke client authentication if 401 is received */
            responseError: function(rejection) {
                if (rejection != null && rejection.status === 401 && ($window.sessionStorage.token || AuthenticationService.isAuthenticated)) {
                    delete $window.sessionStorage.token;
                    AuthenticationService.isAuthenticated = false;
                    $location.path("/admin/login");
                }

                return $q.reject(rejection);
            }
        };
    });

The last part of the above code, responseerror, is actually the part of authorization failure. The processing method is to return to the login authorization page.
The method considered here is to use refresh if the token times out_ In exchange for a new token. This refresh_ The token is issued to the client when it is issued at the beginning. You can’t use the bear token above. You have to deal with the token problem yourself.
Idea 1: record the token timeout in the user and calculate the remaining time. If the remaining time is less than 1 minute, for example, start issuing a new token, and the client will automatically use the new token. When exiting, the new token will not be issued.

thank you!

Please indicate the source of Reprint:http://www.haomou.net/2014/08/13/2014_web_token/

Recommended Today

Blog Garden Background page dynamic effects

1. To set animation, you must first apply for permission 1.1 first enter [my blog park] and enter [settings] in [management] 1.2 find [blog sidebar announcement] and click [apply for JS permission] 1.3 write the content of application JS permission (examples are as follows) Dear blog administrator: Can you open JS permission for me? I […]