{"id":361,"date":"2025-08-27T20:36:05","date_gmt":"2025-08-27T20:36:05","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=361"},"modified":"2025-08-27T20:36:07","modified_gmt":"2025-08-27T20:36:07","slug":"how-to-add-jwt-authentication-to-laravel-apis","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-add-jwt-authentication-to-laravel-apis\/","title":{"rendered":"How to Add JWT Authentication to Laravel APIs"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>How to Add JWT Authentication to Laravel APIs<\/strong><\/h2>\n\n\n\n<p><strong>JWT (JSON Web Tokens)<\/strong> is a stateless auth mechanism ideal for APIs. Clients authenticate once, receive a signed token, and send it in the <code>Authorization: Bearer &lt;token&gt;<\/code> header. In this guide you\u2019ll install a JWT library, issue\/verify tokens, protect routes, refresh\/blacklist tokens, and build a tiny UI to test everything.<\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Install &amp; Configure the JWT Package<\/strong><\/h2>\n\n\n\n<p>We\u2019ll use the popular <code>tymon\/jwt-auth<\/code> package which provides guards, middleware, and helpers for issuing\/validating JWTs.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">composer require tymon\/jwt-auth\n\nphp artisan vendor:publish --provider=<span class=\"hljs-string\">\"Tymon\\JWTAuth\\Providers\\LaravelServiceProvider\"<\/span>\n\nphp artisan jwt:secret<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">Bash<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">bash<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Publishing copies <code>config\/jwt.php<\/code> into your app. Running <code>jwt:secret<\/code> sets <code>JWT_SECRET<\/code> in <code>.env<\/code> and generates a signing key. Keep it private; changing it invalidates all existing tokens.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>2 &#8211; Switch API Guard to JWT<\/strong><\/h2>\n\n\n\n<p>Tell Laravel to use the JWT guard for API routes so <code>auth:api<\/code> resolves the bearer token into the authenticated user.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/auth.php (snippets)<\/span>\n<span class=\"hljs-string\">'guards'<\/span> =&gt; &#91;\n    <span class=\"hljs-string\">'web'<\/span> =&gt; &#91;\n        <span class=\"hljs-string\">'driver'<\/span> =&gt; <span class=\"hljs-string\">'session'<\/span>,\n        <span class=\"hljs-string\">'provider'<\/span> =&gt; <span class=\"hljs-string\">'users'<\/span>,\n    ],\n    <span class=\"hljs-string\">'api'<\/span> =&gt; &#91;\n        <span class=\"hljs-string\">'driver'<\/span> =&gt; <span class=\"hljs-string\">'jwt'<\/span>,    <span class=\"hljs-comment\">\/\/ &lt;-- use the JWT driver<\/span>\n        <span class=\"hljs-string\">'provider'<\/span> =&gt; <span class=\"hljs-string\">'users'<\/span>,\n    ],\n],\n\n<span class=\"hljs-string\">'providers'<\/span> =&gt; &#91;\n    <span class=\"hljs-string\">'users'<\/span> =&gt; &#91;\n        <span class=\"hljs-string\">'driver'<\/span> =&gt; <span class=\"hljs-string\">'eloquent'<\/span>,\n        <span class=\"hljs-string\">'model'<\/span>  =&gt; App\\Models\\User::class,\n    ],\n],<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The <code>jwt<\/code> driver hooks into request lifecycle to validate and decode tokens automatically, making <code>auth('api')<\/code> and <code>$request-&gt;user()<\/code> work as usual for protected endpoints.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Auth Controller: Login, Me, Logout, Refresh<\/strong><\/h2>\n\n\n\n<p>Create a dedicated controller for token issuance and lifecycle operations. We\u2019ll validate credentials, return a signed JWT, expose a <code>me<\/code> endpoint, invalidate tokens on logout, and refresh tokens before expiry.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/JwtAuthController.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Auth<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">JwtAuthController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">login<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $credentials = $request-&gt;validate(&#91;\n            <span class=\"hljs-string\">'email'<\/span> =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>,<span class=\"hljs-string\">'email'<\/span>],\n            <span class=\"hljs-string\">'password'<\/span> =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>]\n        ]);\n\n        <span class=\"hljs-keyword\">if<\/span> (! $token = Auth::guard(<span class=\"hljs-string\">'api'<\/span>)-&gt;attempt($credentials)) {\n            <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;<span class=\"hljs-string\">'message'<\/span> =&gt; <span class=\"hljs-string\">'Invalid credentials'<\/span>], <span class=\"hljs-number\">401<\/span>);\n        }\n\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;respondWithToken($token);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">me<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(Auth::guard(<span class=\"hljs-string\">'api'<\/span>)-&gt;user());\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">logout<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        Auth::guard(<span class=\"hljs-string\">'api'<\/span>)-&gt;logout(); <span class=\"hljs-comment\">\/\/ adds token to blacklist if enabled<\/span>\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;<span class=\"hljs-string\">'message'<\/span> =&gt; <span class=\"hljs-string\">'Logged out'<\/span>]);\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">refresh<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;respondWithToken(Auth::guard(<span class=\"hljs-string\">'api'<\/span>)-&gt;refresh());\n    }\n\n    <span class=\"hljs-keyword\">protected<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">respondWithToken<\/span><span class=\"hljs-params\">(string $token)<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;\n            <span class=\"hljs-string\">'access_token'<\/span> =&gt; $token,\n            <span class=\"hljs-string\">'token_type'<\/span>   =&gt; <span class=\"hljs-string\">'bearer'<\/span>,\n            <span class=\"hljs-string\">'expires_in'<\/span>   =&gt; Auth::guard(<span class=\"hljs-string\">'api'<\/span>)-&gt;factory()-&gt;getTTL() * <span class=\"hljs-number\">60<\/span>\n        ]);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>attempt()<\/code> verifies credentials and returns a JWT on success. <code>logout()<\/code> invalidates the current token (blacklists it if configured). <code>refresh()<\/code> issues a new token using a still-valid one, extending the session without re-entering credentials.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Routes &amp; Middleware<\/strong><\/h2>\n\n\n\n<p>Expose auth endpoints and protect your API routes with <code>auth:api<\/code>. You can also add throttling to slow down brute-force attempts.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/api.php<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">JwtAuthController<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>\\<span class=\"hljs-title\">PostApiController<\/span>;\n\nRoute::post(<span class=\"hljs-string\">'\/auth\/login'<\/span>,    &#91;JwtAuthController::class, <span class=\"hljs-string\">'login'<\/span>])-&gt;middleware(<span class=\"hljs-string\">'throttle:20,1'<\/span>);\nRoute::post(<span class=\"hljs-string\">'\/auth\/refresh'<\/span>,  &#91;JwtAuthController::class, <span class=\"hljs-string\">'refresh'<\/span>]);\nRoute::post(<span class=\"hljs-string\">'\/auth\/logout'<\/span>,   &#91;JwtAuthController::class, <span class=\"hljs-string\">'logout'<\/span>])-&gt;middleware(<span class=\"hljs-string\">'auth:api'<\/span>);\nRoute::get(<span class=\"hljs-string\">'\/auth\/me'<\/span>,        &#91;JwtAuthController::class, <span class=\"hljs-string\">'me'<\/span>])-&gt;middleware(<span class=\"hljs-string\">'auth:api'<\/span>);\n\n<span class=\"hljs-comment\">\/\/ Example protected resource<\/span>\nRoute::middleware(<span class=\"hljs-string\">'auth:api'<\/span>)-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n    Route::get(<span class=\"hljs-string\">'\/posts'<\/span>,  &#91;PostApiController::class, <span class=\"hljs-string\">'index'<\/span>]);\n    Route::post(<span class=\"hljs-string\">'\/posts'<\/span>, &#91;PostApiController::class, <span class=\"hljs-string\">'store'<\/span>]);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Only authenticated requests with a valid bearer token can reach the protected group. The login route is throttled to mitigate password spraying; adjust limits for your environment.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Token TTL, Refresh &amp; Blacklist<\/strong><\/h2>\n\n\n\n<p>Configure expiry and blacklist behavior to balance security and UX. Short TTL + refresh rotation is a good default.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ config\/jwt.php (snippets)<\/span>\n<span class=\"hljs-string\">'ttl'<\/span> =&gt; env(<span class=\"hljs-string\">'JWT_TTL'<\/span>, <span class=\"hljs-number\">60<\/span>),            <span class=\"hljs-comment\">\/\/ minutes, e.g. 60<\/span>\n<span class=\"hljs-string\">'refresh_ttl'<\/span> =&gt; env(<span class=\"hljs-string\">'JWT_REFRESH_TTL'<\/span>, <span class=\"hljs-number\">20160<\/span>), <span class=\"hljs-comment\">\/\/ minutes (14 days)<\/span>\n<span class=\"hljs-string\">'blacklist_enabled'<\/span> =&gt; env(<span class=\"hljs-string\">'JWT_BLACKLIST_ENABLED'<\/span>, <span class=\"hljs-keyword\">true<\/span>),<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><code>ttl<\/code> controls access token lifetime. <code>refresh_ttl<\/code> controls how long a token can be refreshed. With blacklist enabled, <code>logout()<\/code> immediately invalidates the token even if it hasn\u2019t expired, preventing reuse.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Optional: Add Custom Claims<\/strong><\/h2>\n\n\n\n<p>You can embed extra, non-sensitive info in the JWT (e.g., role, plan). Use claims for authorization hints\u2014not for secrets.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Models\/User.php (snippet)<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Tymon<\/span>\\<span class=\"hljs-title\">JWTAuth<\/span>\\<span class=\"hljs-title\">Contracts<\/span>\\<span class=\"hljs-title\">JWTSubject<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">User<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Authenticatable<\/span> <span class=\"hljs-keyword\">implements<\/span> <span class=\"hljs-title\">JWTSubject<\/span>\n<\/span>{\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getJWTIdentifier<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">$this<\/span>-&gt;getKey();\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getJWTCustomClaims<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> &#91;\n            <span class=\"hljs-string\">'role'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;role ?? <span class=\"hljs-string\">'user'<\/span>,\n            <span class=\"hljs-string\">'plan'<\/span> =&gt; <span class=\"hljs-keyword\">$this<\/span>-&gt;plan ?? <span class=\"hljs-string\">'free'<\/span>,\n        ];\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Implementing <code>JWTSubject<\/code> allows the library to serialize the user into the token. Custom claims travel with the token and are available after decode, handy for quick policy checks.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; CORS &amp; Frontend Usage<\/strong><\/h2>\n\n\n\n<p>SPAs\/mobile apps must send the bearer token on every request. If your frontend runs on a different origin, enable CORS so browsers allow the calls.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Kernel.php (snippet)<\/span>\n<span class=\"hljs-keyword\">protected<\/span> $middleware = &#91;\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    \\Fruitcake\\Cors\\HandleCors::class,\n];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Use the official CORS middleware (already present in new Laravel apps). Configure allowed origins, methods, and headers in <code>config\/cors.php<\/code>. Always send <code>Authorization: Bearer &lt;token&gt;<\/code> from the client.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; UI: Minimal JWT Tester<\/strong><\/h2>\n\n\n\n<p>This tiny page logs in to get a token, calls a protected endpoint, refreshes the token, and logs out \u2014 all from the browser.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-comment\">&lt;!-- resources\/views\/jwt\/tester.blade.php --&gt;<\/span>\n@extends('layouts.app')\n\n@section('content')\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"container\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>JWT Tester<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"row g-2\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"col-md-4\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"email\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"form-control\"<\/span> <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Email\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"col-md-4\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"password\"<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"password\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"form-control\"<\/span> <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Password\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mt-3\"<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-theme\"<\/span> <span class=\"hljs-attr\">onclick<\/span>=<span class=\"hljs-string\">\"login()\"<\/span>&gt;<\/span>Login<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-secondary ms-2\"<\/span> <span class=\"hljs-attr\">onclick<\/span>=<span class=\"hljs-string\">\"me()\"<\/span>&gt;<\/span>Call \/auth\/me<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-warning ms-2\"<\/span> <span class=\"hljs-attr\">onclick<\/span>=<span class=\"hljs-string\">\"refresh()\"<\/span>&gt;<\/span>Refresh Token<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-danger ms-2\"<\/span> <span class=\"hljs-attr\">onclick<\/span>=<span class=\"hljs-string\">\"logout()\"<\/span>&gt;<\/span>Logout<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">pre<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"out\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"mt-3\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">pre<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/cdn.jsdelivr.net\/npm\/axios\/dist\/axios.min.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n<span class=\"hljs-keyword\">let<\/span> token = <span class=\"hljs-literal\">null<\/span>;\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">login<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  axios.post(<span class=\"hljs-string\">'\/api\/auth\/login'<\/span>, {\n    <span class=\"hljs-attr\">email<\/span>: <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'email'<\/span>).value,\n    <span class=\"hljs-attr\">password<\/span>: <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'password'<\/span>).value\n  }).then(<span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> {\n    token = r.data.access_token;\n    <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = <span class=\"hljs-built_in\">JSON<\/span>.stringify(r.data, <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-number\">2<\/span>);\n  }).catch(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> out(e));\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">me<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  axios.get(<span class=\"hljs-string\">'\/api\/auth\/me'<\/span>, { <span class=\"hljs-attr\">headers<\/span>: { <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${token}<\/span>`<\/span> }})\n    .then(<span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = <span class=\"hljs-built_in\">JSON<\/span>.stringify(r.data, <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-number\">2<\/span>))\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> out(e));\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">refresh<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  axios.post(<span class=\"hljs-string\">'\/api\/auth\/refresh'<\/span>, {}, { <span class=\"hljs-attr\">headers<\/span>: { <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${token}<\/span>`<\/span> }})\n    .then(<span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> { token = r.data.access_token; <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = <span class=\"hljs-built_in\">JSON<\/span>.stringify(r.data, <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-number\">2<\/span>) })\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> out(e));\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">logout<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  axios.post(<span class=\"hljs-string\">'\/api\/auth\/logout'<\/span>, {}, { <span class=\"hljs-attr\">headers<\/span>: { <span class=\"hljs-attr\">Authorization<\/span>: <span class=\"hljs-string\">`Bearer <span class=\"hljs-subst\">${token}<\/span>`<\/span> }})\n    .then(<span class=\"hljs-function\"><span class=\"hljs-params\">r<\/span> =&gt;<\/span> <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = <span class=\"hljs-built_in\">JSON<\/span>.stringify(r.data, <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-number\">2<\/span>))\n    .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> out(e));\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">out<\/span>(<span class=\"hljs-params\">e<\/span>) <\/span>{\n  <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = e.response ? <span class=\"hljs-built_in\">JSON<\/span>.stringify(e.response.data, <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-number\">2<\/span>) : e;\n}\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n@endsection<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The page demonstrates the full lifecycle: obtain, use, refresh, and revoke tokens. Perfect for quick end-to-end verification before wiring up your SPA or mobile client.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>You added JWT authentication to a Laravel API using <code>tymon\/jwt-auth<\/code>, switched the guard, built endpoints to login\/refresh\/logout, protected routes, and tested the flow with a simple UI. JWT keeps servers stateless and scales well. Combine it with HTTPS, short TTLs, token rotation, and blacklist on logout to balance security and usability.<\/p>\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/building-a-mobile-app-backend-with-laravel-12-api\">Building a Mobile App Backend with Laravel API<\/a><\/li>\n<li><a href=\"\/blog\/integrating-laravel-with-third-party-apis-mail-sms-payment\">Integrating Laravel with Third-Party APIs (Mail, SMS, Payment)<\/a><\/li>\n<li><a href=\"\/blog\/how-to-build-a-multi-auth-api-with-laravel-sanctum\">How to Build a Multi-Auth API with Sanctum<\/a><\/li>\n<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>How to Add JWT Authentication to Laravel APIs JWT (JSON Web Tokens) is a stateless auth mechanism ideal for APIs. Clients authenticate once, receive a signed token, and send it in the Authorization: Bearer &lt;token&gt; header. In this guide you\u2019ll install a JWT library, issue\/verify tokens, protect routes, refresh\/blacklist tokens, and build a tiny UI [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":365,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[25,58,22],"class_list":["post-361","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-api","tag-jwt","tag-security"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/361","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/comments?post=361"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/361\/revisions"}],"predecessor-version":[{"id":364,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/361\/revisions\/364"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/365"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=361"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=361"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=361"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}