JWT Authentication (JSON Web Token)
Configuration and use of token-based authentication.
Introduction
JWT (JSON Web Token) is a standard for ensuring the security and context of authentication in web applications, where the frontend is decoupled from the backend.
It is commonly used when the frontend of a web application is developed in ReactJS, AngularJS, VueJS, etc. and the backend provides a REST API to supply and receive dynamic application data.
The token (JWT) is a secure code that identifies the user.
With JWT (JSON Web Token) the backend's REST API allows private services to obtain the authentication context, in order to know who the authenticated user is, that is, from the token we can know which user it is, and the token in this case is JWT (JSON Web Token).

CORS
The REST API may be on a different subdomain from the website, which implies CORS (Cross-Origin Resource Sharing) configurations so that the services can be accessed.
Therefore, if the REST API is on a different domain/subdomain from the frontend, even when using JWT, it is necessary that CORS is correctly configured.
CORS is a security policy for consuming resources on different domains, subdomains or ports. To ensure that one address can actually integrate with another address.
This happens because browsers block the use of services, or other types of resources, on external addresses, aiming to prevent attacks that obtain confidential data.
Activation and Configuration
To activate and configure JWT in your Netuno application, you must edit the application's configuration file for the environment you are using, such as:
config/_development.jsonconfig/_production.json
It depends on the environment configured in Netuno, in the
netuno/config.jsfile see theconfig.envparameterization.
Insert and adjust the following parameters:
...
"auth": {
"jwt": {
"enabled": true,
"secret": "SecretMustHave32Characters!!!",
"expires": {
"access": 1440,
"refresh": 1440
}
}
},
...
In the secret parameter put a complex and random sequence of characters, it is the key that will guarantee the
security of the JWT encryption.
The expires parameters are defined in minutes, for example: 60 which equals 1 hour and 1440 which equals one day.
Restricted Access to Groups
Additionally, the groups parameter can be passed, which defines the code of the Groups of users who can
authenticate with JWT, for example:
...
"auth": {
"jwt": {
...
"groups": ["customer", "supplier"],
...
}
},
...
In the example above, only the users who belong to the groups of:
- Customers, with the group code
customer. - Suppliers, with the group code
supplier
Only users in these groups can authenticate with JWT.
How to Obtain the Access Token
To obtain the JWT Access Token Netuno provides the _auth service, which validates the authentication and, if it is
successful, returns the Access Token and the Refresh Token.
The Access Token is obtained as follows:
Fetch - Pure JavaScript
JavaScript in browsers provides fetch to integrate with the backend's REST API services.
Below are some examples of how to perform authentication with JWT and token renewal.
Authentication
Example of how to obtain the Access Token with fetch:
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
username: payload.username,
password: payload.password,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('Authentication failed with unexpected status:', response.status);
}
alert('Invalid login.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`My Access Token: ${res.access_token}`);
console.log(`My Refresh Token: ${res.refresh_token}`);
console.log(`Expires in: ${res.expires_in} minutes`);
sessionStorage.setItem('token', JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('There was a technical problem, please try again later.');
});
Note that the token must be kept as a session, so it is preferable to use sessionStorage
instead of localStorage for greater security.
With the refresh_token it is possible to generate a new token before the expiration time (expires_in in minutes).
Using the Access Token in Other Services
To execute custom-programmed services that require prior authentication in Netuno applications, you must pass the Access Token in the HTTP protocol Header, as the value of the Authorization key, for example:
Authorization: Bearer eyJhbGciOiJIUzU...
Example of how to execute a custom-programmed service through the frontend using fetch, where the
Authorization is passed in the headers:
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/my-service", {
method: 'post',
credentials: 'include',
headers: {
'Authorization': `${token.token_type} ${token.access_token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
myParameter1: '...',
myParameter2: '...'
})
}).then((response) => {
if (response.status != 200) {
console.log(`Service failed with status ${response.status}.`);
if (response.status == 403) {
alert('Not authenticated.');
} else {
alert('There was a failure with the service.');
}
return null;
}
return response.json();
}).then((res) => {
if (res) {
console.log(`Response data from my service:`, res);
alert('Service executed successfully.');
}
}).catch((error)=> {
console.log(error);
alert('There was a technical problem, please try again later.');
});
Note that, in the HTTP Headers, in the Authorization parameter the token_type and the access_token
obtained from the JWT object previously saved during authentication in
sessionStorage are used.
Refresh with the Refresh Token
To refresh the token, and extend the authentication, you must call the _auth service again, but in addition to the
jwt: true parameter you also need to pass the refresh_token parameter with the value received in the previously
successful authentication.
The token refresh must be done before the expiration time (expires_in in minutes).
So to extend the authentication and renew the token, see the example:
const token = JSON.parse(sessionStorage.getItem("token"));
fetch("http://localhost:9000/services/_auth", {
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
refresh_token: token.refresh_token,
jwt: true
})
}).then((response) => {
if (response.status != 200) {
if (response.status != 403) {
console.log('Token renewal failed with unexpected status:', response.status);
}
alert('Access renewal with invalid token.');
return null;
}
return response.json();
}).then((res) => {
if (res && res.result === true) {
console.log(`My New Access Token: ${res.access_token}`);
console.log(`My New Refresh Token: ${res.refresh_token}`);
console.log(`Expires in: ${res.expires_in} minutes`);
sessionStorage.setItem("token", JSON.stringify(res));
}
}).catch((error)=> {
console.log(error);
alert('There was a technical problem, please try again later.');
});
When you obtain the new token you must start using it, instead of the previous (old) one, in the next calls to the services.
This way the authentication is extended with successive token renewals, it is a way of ensuring that the user remains authenticated.
Auth Client - NPM
To easily perform the integration with the frontend, the NPM module is provided:
In the frontend you can install it with Bun:
bun install @netuno/auth-client
Another frontend dependency manager such as PNPM, NPM, YARN, or another can be used.
Configuration
This module depends on @netuno/service-client, when defining the services address, for example:
import _service from '@netuno/service-client';
...
_service.config({
prefix: 'http://localhost:9000/services/'
});
The auth-client uses the service-client prefix to internally build the address of the _auth service,
in this case, following the example it will be:
http://localhost:9000/services/_auth
So this address is automatically built using the services address prefix of the REST API of the Netuno application.
See how to customize the auth-client settings:
In the ES module version, config() is asynchronous. Wait for it to complete before using the configured client:
import _auth from '@netuno/auth-client';
await _auth.config({
storage: 'local', // session is the default value.
onLogin: () => { alert("Logged in!"); },
onLogout: () => { alert("Logged out!"); }
});
If you want the token to be saved in localStorage then you must define storage: 'local' in the configuration,
because by default it is storage: 'session' using sessionStorage.
Authentication
To perform the login using _auth:
import _auth from '@netuno/auth-client';
...
_auth.login({
username: "admin",
password: "secret",
success: ()=> {
alert("Success.");
},
fail: ()=> {
alert("Fail.");
}
});
The token is saved automatically, in session (sessionStorage) or local (localStorage), depending on the
configuration, by default it is session.
Refresh the Token
Example of how to refresh the token:
import _auth from '@netuno/auth-client';
...
_auth.refreshToken({
success: ()=> {
alert("Success.");
},
fail: ()=> {
alert("Fail.");
}
});
The token is saved automatically, in session (sessionStorage) or local (localStorage), depending on the
configuration, by default it is session.
Remove the Token
Example of how to end the session, that is, the process of logging out and deleting the token:
import _auth from '@netuno/auth-client';
...
_auth.logout();
The token is removed from sessionStorage or from localStorage depending on the configuration, the default is
sessionStorage.
Backend - REST API Services
If the Authorization header is passed in the integration with the services with a valid token, then it is possible
to identify which user and group it is.
In the backend we can use the _user and _group resources, which identify which user and group
respectively.
To see the data of the authenticated user we use the User resource, for example:
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
_log.info(
"Authenticated user:",
_val.map()
.set("id", _user.id())
.set("name", _user.name())
.set("code", _user.code())
.set("all", _user.data())
)
To see the group of the authenticated user we use the Group resource, for example:
- JavaScript
- TypeScript
- Python
- Ruby
- Kotlin
- Groovy
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
_log.info(
"Authenticated group:",
_val.map()
.set("id", _group.id())
.set("name", _group.name())
.set("code", _group.code())
.set("all", _group.data())
)
Security
If the token is invalid, or if the Authorization is not passed, then Netuno ensures that the service is not
executed, unless the service is configured as public access, by default services are private.
To manage whether the service is public or private configure in your application at:
server/core/_service_config.js
Since in development Netuno allows private services to be executed directly without authentication, for practicality reasons, because most custom-programmed services normally do not need the authentication context, so in general development and testing become simpler.
If it is not in development, for example in production, then the services can only be executed with authentication, as is obvious.
Authentication Resource
Normally the _user and _group resources are enough, but there are situations where we want to perform more
advanced operations and in this case we have the _auth resource.
The _auth resource is the main authentication resource, it manages the configurations and has all the operations
related to the authentication process.
To learn more, see the documentation of the Auth resource.
ReAuthKit
Netuno provides a base project to create platforms with online authentication, which includes account creation, login, profile, avatar, password recovery, and much more.
Check it out on GitHub:
ReAuthKit is a Netuno application with the frontend made in React, Ant.Design and React Router, and the database in PostgreSQL.
With ReAuthKit the creation of new projects with authentication is greatly accelerated, because it already has all the mechanisms needed for any project that requires authentication.
Conclusion
In a simple way, it is possible to configure and activate authentication with JWT (JSON Web Token) in applications.
Use JWT authentication to develop secure services in the REST API.
The JWT integration in the frontend can be done directly with fetch.
To speed up and standardize the implementation, the NPM module @netuno/auth-client is provided, which can be used with any frontend technology.
Whenever you need to identify the user when executing REST API services, use JWT authentication.
In the backend, configuration and obtaining the authenticated user are very simple.