-
-
Notifications
You must be signed in to change notification settings - Fork 11
2.4: Double JWT
Recommended for apps
The best way to connect any kind of app to your endpoint is to enable double JWT authentication. Double JWT means, that an authenticated user gets a longer-living refresh-token and an access-token with only a short life. The access-token is used to legitimate any request. The refresh-token lets you get a new access-token, if the old one is expired. So, if anyone manages to intercept one of your requests and snatches a token, he can wreak havoc only temporarily until it expires.
An application with double-JWT authentication needs two different secrets that are configurable in the configuration-screen. The "Token Secret" is used to sign the refresh-tokens. The value of "Access-token Secret" is used, as you may already suspect, to sign all access-tokens. A random token secret with a reasonable length of 52 characters will be prefilled in a newly created application. But of course you have the possibility to change it to a custom value.
Let us now turn to the request. I will demonstrate it with simple Angular-code examples, but you surely can do the same thing in any other programming language as well. To get your first refresh-token you simply have to login with a valid username/password combination:
// Example with username/pass as a basic-authorisation header (recommended):
this.httpClient.post('https://my-website.dev/api/auth', undefined, {
'x-api-key': 'ytaaYCMkUmouZawYMvJN9',
'authorization': 'Basic ' + btoa(username + ':' + pass),
});
// Alternatively you can send username/pass in the request-body:
this.httpClient.post(
'https://my-website.dev/api/auth',
JSON.stringify({
username: username,
password: pass,
}),
{
'x-api-key': 'ytaaYCMkUmouZawYMvJN9',
}
);The resulting JSON response contains your refresh-token and the name of the authenticated user:
If you are interested in the data which is included in the token, you can easily decode it (for example online under https://jwt.io). The data is signed, so it cannot be changed without knowing the server's secret. A token that is generated from my AppApi-module contains the following data:
{
"iss": "my-website.local", // issuer-claim: your domain
"aud": 4, // audition-claim: the internal ID of your application
"sub": 41, // subject-claim: userID of the authenticated user
"iat": 1594749695, // issued-at-claim: creation-time
"nbf": 1594749695, // not-before-claim: creation-time
"jti": "J7cNWl3rtCuQICwk06aoK", // JWT-ID: unique id
"exp": 1597341695 // expires-claim: token-expiration-timestamp
}Every claim of an incoming token will be validated. If anything doesn't match, the token will be revoked.
The next step is to get an access-token, which you need to legitimize your api-requests. A refresh-token is only useful to apply for an access-token, nothing more. The target of your next api-request is the /auth/access-endpoint. Attach your refresh-token as a bearer token like I do in the following Angular-request:
this.httpClient.post('https://my-website.dev/api/auth/access', {
'x-api-key': 'ytaaYCMkUmouZawYMvJN9',
authorization: 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS13ZWJzaXRlLmxvY2FsIiwiYXVkIjo0LCJzdWIiOjQxLCJpYXQiOjE1OTQ3NDk2OTUsIm5iZiI6MTU5NDc0OTY5NSwianRpIjoiSjdjTldsM3J0Q3VRSUN3azA2YW9LIiwiZXhwIjoxNTk3MzQxNjk1LCJzaWQiOiIxNDJhZmVhZG9jOTIybzJzZWJpaDciLCJzaWRfY2hhbGxlbmdlIjoiYmwwMzQyYXNpS0JIVS81a0g0Q0xWWGNzYWlNMTExOCJ9.9y4h0nslg2JHLSPFevOK-JWx2P_RfaaqSHRPi7nnSMk',
});With the response you will get both - an access-token and a new refresh-token:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS13ZWJzaXRlLmxvY2FsIiwiYXVkIjoyLCJzdWIiOjQxLCJpYXQiOjE1OTQ4Mzc1OTYsIm5iZiI6MTU5NDgzNzU5NiwianRpIjoiV25jNHlxSkF0dWJhb3NiMWhEMGYiLCJleHAiOjE1OTQ5MjM5OTYsInNpZCI6ImowNm0ydnZzZWJpZDAxNmpwZWdtZm9ld2kiLCJzaWRfY2hhbGxlbmdlIjoiQUJMcy5ibzEuVGppLklJRGxvNDJETFVaQy5aIiwicnRrbiI6Iko3Y05XbDNydEN1UUlDd2swNmFvSyJ9.x7_Yvq_WekIgWFZEpa4LEdTyhPEajhhYQF58bvG-ZFQ",
"refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS13ZWJzaXRlLmxvY2FsIiwiYXVkIjoyLCJzdWIiOjQxLCJpYXQiOjE1OTQ4Mzc0NDIsIm5iZiI6MTU5NDgzNzQ0MiwianRpIjoiSjdjTldsM3J0Q3VRSUN3azA2YW9LIiwiZXhwIjoxNTk3NDI5NTk2fQ.yAuESeRaB5f-RRlkPisLVTyrCDWr_h4MeqmyOqflVGQ"
}A refresh-token expires right after its usage. Because of that, when your access-token gets invalid, you have to use this new refresh-token to ask for a new one.
With your access-token you can now make authenticated requests! You only have to add it as a Bearer-authentication header:
this.httpClient.get('https://my-website.dev/api/auth', {
'x-api-key': 'ytaaYCMkUmouZawYMvJN9',
authorization:
'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS13ZWJzaXRlLmxvY2FsIiwiYXVkIjo0LCJzdWIiOjQxLCJpYXQiOjE1OTQ3NDk2OTUsIm5iZiI6MTU5NDc0OTY5NSwianRpIjoiSjdjTldsM3J0Q3VRSUN3azA2YW9LIiwiZXhwIjoxNTk3MzQxNjk1LCJzaWQiOiIxNDJhZmVhZG9jOTIybzJzZWJpaDciLCJzaWRfY2hhbGxlbmdlIjoiYmwwMzQyYXNpS0JIVS81a0g0Q0xWWGNzYWlNMTExOCJ9.9y4h0nslg2JHLSPFevOK-JWx2P_RfaaqSHRPi7nnSMk',
});This request proves, that I am now authenticated:
{
"id": 41,
"name": "sebi",
"loggedIn": true
}Without the authorization-header, I would only be a not-authenticated guest:
{
"id": 40,
"name": "guest",
"loggedIn": false
}Yay! It works!
But wait! There is one more thing, that I want to mention relating to double JWT authentication. Mostly JWTs are used for authentication if we want to make it possible without storing any information about the user or the session on the server. As you have seen, our token contains all necessary information inside its data-claims. And it is signed with a secret key, which only or server knows. So nobody can manipulate any information of the token.
Although this works really great, I still wanted to have a little more control over the tokens that are given. So nevertheless, a reference to every double-JWT-token is stored in the module's database. You find a list of all active token-sessions in the configuration-view of your application.

If a user logs out, the session will automatically disappear from the list. You, as the admin, are able to force logout a user as well. Simply delete it from the list. With this action, the user is forced to login and create a new session. All tokens, that were created in relation to the old token are immediately invalid.
➡️ Continue with 3: Creating Endpoints
⬅️ Back to 2.3: Single JWT
AppApi
Connect your apps to ProcessWire!
| ProcessWire-Module: | https://modules.processwire.com/modules/app-api/ |
| Support-Forum: | https://processwire.com/talk/topic/24014-new-module-appapi/ |
| Repository: | https://github.com/Sebiworld/AppApi |
| Wiki: | https://github.com/Sebiworld/AppApi/wiki |
{ "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS13ZWJzaXRlLmxvY2FsIiwiYXVkIjoyLCJzdWIiOjQxLCJpYXQiOjE1OTQ4Mzc0NDIsIm5iZiI6MTU5NDgzNzQ0MiwianRpIjoiaHd3NDlRSUN3azA2YTRrMXdGYmdWSiIsImV4cCI6MTU5NzQyOTQ0Mn0.fgWGmwzabHcecAzrPDxYf66Ie1Z0Vxl-H3oxMj0Asxc", "username": "sebi" }