{"id":72889,"date":"2025-07-01T11:53:21","date_gmt":"2025-07-01T06:23:21","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=72889"},"modified":"2025-07-13T13:55:13","modified_gmt":"2025-07-13T08:25:13","slug":"woocommerce-api-with-jwt-a-developer-guide","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/woocommerce-api-with-jwt-a-developer-guide\/","title":{"rendered":"How to Create Custom WooCommerce API with Secure JWT Token : A Developer Guide"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>If you are working on a mobile app, a headless site, or just want to connect WooCommerce with some external system, then you wll probably need a secure way to access data via API.<br \/>\nWooCommerce already gives REST API by default, but it works on API keys, which is a bit old-school now. Not that flexible also, especially when you want to handle login-like flows.<\/p>\n<p>So a better way is to just create your own REST API endpoints and protect them using JWT (JSON Web Token). JWT is easy, clean, and supports modern auth flows like login, logout, token-based access, etc.<br \/>\nSo here, I\u2019m just building a custom REST API for WooCommerce and validating it with JWT for security. That way, external apps can connect safely without messing around with the traditional WooCommerce API keys. No need to rely on WooCommerce\u2019s legacy API or generate API keys manually.<\/p>\n<h2>Why Use JWT Instead of WooCommerce API Keys?<\/h2>\n<p>WooCommerce\u2019s default REST API uses key\/secret authentication, but this setup has some limitations:<\/p>\n<ul style=\"list-style-type: circle;\">\n<li style=\"text-align: left;\">Requires manual key setup via the WooCommerce admin<\/li>\n<li style=\"text-align: left;\">No token expiry or session logic<\/li>\n<li style=\"text-align: left;\">Not suitable for modern Single Page Applications (SPA) or mobile apps<\/li>\n<\/ul>\n<p>Instead, JWT (JSON Web Token) is a modern, stateless solution that allows:<\/p>\n<ul style=\"list-style-type: circle;\">\n<li>Login-based token generation<\/li>\n<li>Role\/permission control<\/li>\n<li>Expiry-based sessions<\/li>\n<li>Easy integration with mobile\/JavaScript apps<\/li>\n<\/ul>\n<p><strong>Requirements<\/strong><br \/>\nMake sure the following things are ready in your development setup:<\/p>\n<ol>\n<li>WooCommerce Plugin installed and active<\/li>\n<li>Permalinks set to anything except &#8220;Plain&#8221;<br \/>\nGo to <strong>Settings<\/strong> &gt; <strong>Permalinks<\/strong> [select Post name or Day and name]<\/li>\n<li>PHP version 7.4+<\/li>\n<li>Composer installed globally (we\u2019ll use it to install the JWT library)<\/li>\n<\/ol>\n<h2>Plugin Folder Structure Overview<\/h2>\n<p>We\u2019ll create a custom plugin called <strong>jwt-token-wc-api<\/strong> . Here&#8217;s what the folder layout will look like:<\/p>\n<p><strong>In Project Root: wp-content\/plugin\/<\/strong><br \/>\n<strong>Create a Folder Named &#8211; jwt-token-wc-api\/<\/strong><\/p>\n<ul>\n<li>jwt-token-wc-api.php<\/li>\n<li>\u00a0includes\/<br \/>\n&#8211; class-jwt-token-helper.php<\/li>\n<li>vendor\/<br \/>\n&#8211; autoload.php<\/li>\n<\/ul>\n<p><strong>Step 1: First, install the PHP Library for JWT using Composer<\/strong><br \/>\nFor the Library, we just need to open our terminal, and in the plugin folder, we need to run the below command in the terminal:<\/p>\n<pre>composer require firebase\/php-jwt<\/pre>\n<p>This command will create a Firebase JWT package in the Vendor folder that we will use to generate a token later.<\/p>\n<p><strong>Step 2: Now add the JWT Token Secret-Key in This File (wp-config.php)<\/strong><br \/>\nIn the WordPress project, open the wp-config.php file and add the following code.<\/p>\n<pre>define('<strong>JWT_Token_AUTH_SECRET_KEY<\/strong>', 'secure-jwt-secret-key');<\/pre>\n<p><strong>Step 3: Create a JWT Token Helper Class<br \/>\n<\/strong>Now we will create a helper class that will handle JWT logic like generating and verifying tokens.<br \/>\nwe will create a new file inside our plugin: <strong>includes\/class-jwt-token-helper.php<\/strong><\/p>\n<pre>&lt;?php\r\n\r\n\/\/ Block file accessed if directly trying to open it.\r\nif (!defined('ABSPATH')) {\r\n  exit;\r\n}\r\n\r\n\/\/ Load the Composer autoloader class.\r\nrequire_once plugin_dir_path(__DIR__) . '..\/vendor\/autoload.php';\r\n\r\nuse Firebase\\JWT\\JWT;\r\nuse Firebase\\JWT\\Key;\r\n\r\nclass JWT_Token_Manager {\r\n\r\n\/\/ Getting jwt token secret key from wp-config\r\n   const JWT_Token_Key= JWT_Token_AUTH_SECRET_KEY;\r\n\r\n\/**\r\n* Function to create token for given user-id.\r\n* 1 hour token validity\r\n*\/\r\npublic static function create_user_jwt_token($user_id) {\r\n   $jwt_token_data = [\r\n     'issuer' =&gt; get_site_url(),\r\n     'issued_at' =&gt; time(),\r\n     'expires_at' =&gt; time() + 3600,\r\n     'user_id' =&gt; $user_id,\r\n  ];\r\n\r\n   return JWT::encode($jwt_token_data, self::JWT_Token_Key, 'HS256');\r\n}\r\n\r\n\/**\r\n* Check JWT Token from Incoming API Request.\r\n*\/\r\npublic static function validate_request_token(WP_REST_Request $request) {\r\n   $auth_header = $request-&gt;get_header('authorization');\r\n\r\n\/\/ If no jwt token found then showing this error.\r\nif (!$auth_header || !preg_match('\/Bearer\\\\s(\\\\S+)\/', $auth_header, $matches)) {\r\n   return new WP_Error('token_missing', 'Authorization header is missing.', ['status' =&gt; 401]);\r\n}\r\n\r\n$jwt_token = sanitize_text_field($matches[1]);\r\n\r\ntry {\r\n\/\/ Decoding and verify the jwt token.\r\n   $decoded_jwt_token = JWT::decode($jwt_token, new Key(self::JWT_Token_Key, 'HS256'));\r\n   return true;\r\n  } catch (Exception $e) {\r\n   return new WP_Error('token_invalid', 'The provided token is invalid or expired.', ['status' =&gt; 403]);\r\n  }\r\n }\r\n}<\/pre>\n<p><strong>Step 4: Create Now Main-Plugin File.<\/strong><br \/>\nWe will now create a new file inside our plugin folder named: <strong>jwt-token-wc-api.php<\/strong><\/p>\n<pre>&lt;?php\r\n\/**\r\n* Plugin Name: Secure WooCommerce API with JWT Token\r\n* Description: Adds custom WooCommerce REST API with JWT login \u2013 handy if you're building mobile apps or need to connect outside system.\r\n* Version: 1.0.0\r\n* Author: TO THE NEW Private Limited\r\n*\/\r\n\r\n\/\/ Block file accessed if directly trying to open it.\r\nif (!defined('ABSPATH')) {\r\n  exit;\r\n}\r\n\r\n\/\/ Load the jwt token helper class file.\r\n  require_once plugin_dir_path(__FILE__) . 'includes\/class-jwt-token-helper.php';\r\n\r\n\/**\r\n* Registering the login route to issue secure JWT token.\r\n*\/\r\nadd_action('rest_api_init', function () {\r\n   register_rest_route('jwt-token\/v1', '\/login', [\r\n   'methods' =&gt; 'POST',\r\n   'callback' =&gt; 'handle_jwt_user_api_login',\r\n   'permission_callback' =&gt; '__return_true',\r\n     'args' =&gt; [\r\n       'username' =&gt; ['required' =&gt; true],\r\n       'password' =&gt; ['required' =&gt; true],\r\n     ],\r\n   ]);\r\n});\r\n\r\n\/**\r\n* Callback funciton to authenticate username &amp; password and return JWT token.\r\n*\/\r\nfunction handle_jwt_user_api_login(WP_REST_Request $request) {\r\n   $username = sanitize_text_field($request-&gt;get_param('username'));\r\n   $password = $request-&gt;get_param('password');\r\n\r\n   $user = wp_authenticate($username, $password);\r\n\r\n   if (is_wp_error($user)) {\r\n     return new WP_Error('login_failed', 'Username or password is incorrect.', ['status' =&gt; 403]);\r\n   }\r\n\r\n   if (!user_can($user, 'read')) {\r\n     return new WP_Error('not_allowed', 'You do not have permission to use the API.', ['status' =&gt; 403]);\r\n   }\r\n\r\n   $jwt_token = JWT_Token_Manager::create_user_jwt_token($user-&gt;ID);\r\n\r\n   return [\r\n     'token' =&gt; $jwt_token,\r\n     'user' =&gt; [\r\n     'email' =&gt; $user-&gt;user_email,\r\n     'name' =&gt; $user-&gt;display_name,\r\n   ],\r\n ];\r\n}\r\n\r\n\/**\r\n* Registering a secure API endpoint to fetch WooCommerce order details by order id.\r\n*\/\r\nadd_action('rest_api_init', function () {\r\n   register_rest_route('order-jwt-api\/v1', '\/order', [\r\n     'methods' =&gt; 'GET',\r\n     'callback' =&gt; 'get_woocommerce_order_data_by_id',\r\n     'permission_callback' =&gt; ['JWT_Token_Manager', 'validate_request_token'],\r\n     'args' =&gt; [\r\n       'order_id' =&gt; ['required' =&gt; true, 'type' =&gt; 'integer'],\r\n     ],\r\n   ]);\r\n});\r\n\r\n\/**\r\n* Fetch WooCommerce order data by order-id (JWT protected).\r\n*\/\r\nfunction get_woocommerce_order_data_by_id(WP_REST_Request $request) {\r\n   $order_id = $request-&gt;get_param('order_id');\r\n   $order = wc_get_order($order_id);\r\n\r\n   if (!$order) {\r\n     return new WP_Error('order_not_found', 'No order found for this ID.', ['status' =&gt; 404]);\r\n   }\r\n\r\n   return [\r\n     'order_id' =&gt; $order-&gt;get_id(),\r\n     'status' =&gt; $order-&gt;get_status(),\r\n     'total' =&gt; $order-&gt;get_total(),\r\n     'customer' =&gt; $order-&gt;get_billing_first_name() . ' ' . $order-&gt;get_billing_last_name(),\r\n     'items' =&gt; array_map(function ($item) {\r\n       return [\r\n        'product_id' =&gt; $item-&gt;get_product_id(),\r\n        'name' =&gt; $item-&gt;get_name(),\r\n        'quantity' =&gt; $item-&gt;get_quantity(),\r\n        'line_total' =&gt; $item-&gt;get_total(),\r\n      ];\r\n     }, $order-&gt;get_items()),\r\n   ];\r\n}<\/pre>\n<h2>Now Let&#8217;s Test Our Custom API Using Postman:<\/h2>\n<p><strong>Step 1: First, Get the JWT Token Using the Username &amp; Password<\/strong><br \/>\nJust open Postman and make a POST request to this endpoint: <strong>POST \/wp-json\/jwt-token\/v1\/login<\/strong><\/p>\n<div id=\"attachment_73094\" style=\"width: 635px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-73094\" decoding=\"async\" loading=\"lazy\" class=\"size-large wp-image-73094\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz-1024x529.png\" alt=\"Generate Token\" width=\"625\" height=\"323\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz-1024x529.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz-300x155.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz-768x397.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz-624x322.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/LdKIO31oVz.png 1367w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><p id=\"caption-attachment-73094\" class=\"wp-caption-text\">Generate Token<\/p><\/div>\n<p>You can see in the above image that we are getting the token value on the login endpoint with the correct username\/password<\/p>\n<p><strong>Step 2: Get Order Data Using the JWT Token<br \/>\n<\/strong>We now have a JWT token, let\u2019s fetch the order details using it.<br \/>\nNow we will request this endpoint: <strong>GET \/wp-json\/order-jwt-api\/v1\/order?order_id=&lt;order-id&gt;<\/strong><br \/>\nIn Postman, go to the Authorization tab, choose Bearer Token, and paste our JWT token there.<\/p>\n<div id=\"attachment_73093\" style=\"width: 635px\" class=\"wp-caption aligncenter\"><img aria-describedby=\"caption-attachment-73093\" decoding=\"async\" loading=\"lazy\" class=\"size-large wp-image-73093\" src=\"https:\/\/www.tothenew.com\/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl-1024x589.png\" alt=\"Order Data\" width=\"625\" height=\"359\" srcset=\"\/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl-1024x589.png 1024w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl-300x173.png 300w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl-768x442.png 768w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl-624x359.png 624w, \/blog\/wp-ttn-blog\/uploads\/2025\/07\/hOpWGVn9Yl.png 1370w\" sizes=\"(max-width: 625px) 100vw, 625px\" \/><p id=\"caption-attachment-73093\" class=\"wp-caption-text\">Order Data<\/p><\/div>\n<h2>Conclusion<\/h2>\n<p>JWT login is just easier, honestly. You don\u2019t need to deal with WooCommerce\u2019s default API keys and all that stuff. Once it\u2019s working, you hit your custom routes with the token &#8211; no token, no data, that\u2019s it.<br \/>\nI\u2019ve used this in a few projects like pulling order data for mobile apps, syncing with other platforms, whatever. It keeps things secure, and you don\u2019t need to send your username\/password everywhere. Just works better for custom setups.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction If you are working on a mobile app, a headless site, or just want to connect WooCommerce with some external system, then you wll probably need a secure way to access data via API. WooCommerce already gives REST API by default, but it works on API keys, which is a bit old-school now. Not [&hellip;]<\/p>\n","protected":false},"author":1708,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":42},"categories":[3602],"tags":[7535,5439,7534],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72889"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1708"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=72889"}],"version-history":[{"count":23,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72889\/revisions"}],"predecessor-version":[{"id":73222,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/72889\/revisions\/73222"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=72889"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=72889"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=72889"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}