JWT Authorization
Quick Tip!
Visit the JWT website to experiment with the interactive debugger and learn more about how the tokens work.
Helpful Staff for this Topic
JSON Web Tokens
A JSON Web Token (JWT) can be used to store and securely transmit data as a JSON object. The tokens are signed to verify their validity, and can be encrypted to protect the data it contains. They are usually used to authorize users on websites, without the need to query the database repeatedly or use sessions.
Implementing JWT Authorization
Set Up Config File
To begin working with JWT authorization, you will need to set up your local project’s frontend configuration file. The file must include the following:
- development API base URL
- This will depend on the backend and you will provide this to staff when requesting to have authentication added to the website.
- Ex: https://corps-dashboard-api.azurewebsites.net/public/
- after-login URL
- Like the API base URL, this will depend on the backend and the project needs. You will provide this as part of the initial request.
- Ex: https://corps-dashboard-api.azurewebsites.net/public/after_login
- Shibboleth redirect URL
- Once your request has been completed, you will receive the complete Shibboleth redirect URL with the appropriate querystring parameters.
- Ex: https://devtech-mobilelogin.azurewebsites.net/?appguid=<id>&pid=<platform>&appversion=<version>
Grabbing A Token From Inspect
Before you begin, you may need a valid token from the project to reference during development. You can grab your token using the after-login and Shibboleth url listed above.
Start by visiting the Shibboleth url in your browser. It will prompt you to login, but before doing that, open inspect and navigate to the Network tab.
Once you login, the after login request will be logged in Network. Expand the request information by clicking on it and scroll to the bottom of its headers section. The full JWT will be displayed there as a query string parameter.
Redirect Function in App.js
Once you’ve set up your config file, you will need to write a function to redirect the user to the login site using Shibboleth redirect URL. This function should be called first thing upon loading the website, which can be done through the App.js file for React projects. Once the function is defined, it can be called inside a useEffect hook with an empty dependency array.
The function must check for the JWT token in local storage or in the querystring of the returned URL (?jwt=<token>). It can then either load the website, grab and store the token, or redirect the user to login depending on the result.
const login=()=>{
if(!localStorage.getItem("jwt")){
const params = new URLSearchParams(window.location.search)
if(!params.has("jwt")){
localStorage.setItem("jwt",params.get("jwt"));
}else{
window.location=LOGIN_URL;
}
}
}
Quick Tip!
Note that URLSearchParams is used to get a URL parameter. You can read about it in this MDN article.
During development, the LOGIN_URL will need to be the after-login URL. However, once the project is in production, you can switch it to the Shibboleth redirect URL.
Create After-Login Route
Next, you will need to create the backend route for the after-login endpoint. For example, if the endpoint was ‘http://localhost:8888/project_example/public/after_login?jwt…’ then the route would have to look like this:
<?php
global $app;
$app->group('/after_login', function ($group) {
$group->get('', 'AfterLoginController::afterLogin');
});
Build After-Login Controller
You can now begin to write the backend controller function for the route. The purpose of this function is to grab the JWT token from the endpoint query parameter (after_login?jwt=<token>) and store it as a variable. It can then return the user to the frontend home URL with the token as another querystring parameter. Recall that the function you wrote in the App.js file checks for the token in local storage or in the URL.
You can follow this template function:
public static function afterLogin(Request $request, Response $response, $args){
$jwt = filter_input(INPUT_GET, 'jwt', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
return $response
->withHeader('Location', Config::get('global.home_url').'/?jwt='.$jwt)
->withStatus(302);
}
In the example above, filter_input was used to sanitize the token. You can find the documentation for filter_input on php.net.
Get Public Key
Now that the token is stored in local storage, it needs to be decoded to retrieve the user’s information from the payload. Three things are needed to decode a JWT token:
- The token
- The public key
- The algorithm used to encode the token
If you do not already have the last two, contact the staff member who provided the Shibboleth redirect URL. Once you have the public key, you will need to store it as a .pem file in the config folder of the backend.
Login Model
Before you begin to work on the model, install the Firebase library using the command below:
composer require firebase/php-jwt
With that, you can use the library’s decode function and pass the token, public key, and algorithm as arguments. The public key can be imported as a variable from the .pem file mentioned above, the model can accept the token as a parameter, and the algorithm (either HS256 or RS256) should be passed as shown below:
public static function getUserAfterLogin($token){
$key = file_get_contents(__DIR__."/../config/public.pem");
if($token!=null){
$payload = JWT::decode($token, $key, array('RS256'));
//Complete according to project requirements
}
Authorization Middleware
Now that the token is stored and decoded, you can use the payload with a middleware to authorize the user. First, get the token from the request header ‘Authorization.’ Then, the token can be validated using the login model function. Depending on what the login model returns, an if/else condition can be used to either return the initial request as a response (if the user is authorized) or return a 401 error and “User not authorized” message.
class AuthMiddleware
{
public function __invoke($request, $handler)
{
$token = $request->getHeader("Authorization");
$user = Login::getUserAfterLogin(explode(" ", $token[0])[1]);
//Use explode to get the token without 'Bearer ' from the header
if (//User is authorized) {
$response = $handler->handle($request);
return $response;
} else {
$response = new \Slim\Psr7\Response();
return $response->withStatus(401, "User NOT authorized.");
}
}
}
Update Routes and Requests
Once the authorization middleware is complete, it can be added to the necessary routes. For example, if the website can be viewed by anyone, but only certain staff can edit information on the website, then you would need to add the middleware to any POST, DELETE, and UPDATE routes.
Make sure that an Authorization header is added to all the corresponding requests on the frontend with the parameter ‘Bearer (token from local storage).’