{"id":356,"date":"2025-08-27T20:17:40","date_gmt":"2025-08-27T20:17:40","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=356"},"modified":"2025-08-27T20:17:43","modified_gmt":"2025-08-27T20:17:43","slug":"using-laravel-passport-for-advanced-api-authentication","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/using-laravel-passport-for-advanced-api-authentication\/","title":{"rendered":"Using Laravel Passport for Advanced API Authentication"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\"><strong>Using Laravel Passport for Advanced API Authentication<\/strong><\/h2>\n\n\n\n<p><strong>Laravel Passport<\/strong> brings a full OAuth2 server to your Laravel app\u2014great when you need first-class features beyond simple token auth: password grants, client credentials (machine-to-machine), refresh tokens, and <em>scopes<\/em> for fine-grained permissions. In this guide you\u2019ll install Passport, wire up guards, issue tokens via multiple grants, enforce scopes, and add a tiny UI to test flows quickly.<\/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 Passport &amp; Bootstrap Keys<\/strong><\/h2>\n\n\n\n<p>Add Passport to your project and generate encryption keys and default clients (password &amp; personal access).<\/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 laravel\/passport\n\nphp artisan migrate\nphp artisan passport:install<\/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><code>passport:install<\/code> creates RSA keys and seeds OAuth clients into <code>oauth_clients<\/code>. You\u2019ll get a <em>Password Grant Client<\/em> (for username\/password token exchange), and a <em>Personal Access Client<\/em> for user-issued long-lived tokens (developer tooling \/ first-party use).<\/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; Configure Auth Guard for APIs<\/strong><\/h2>\n\n\n\n<p>Use Passport as the driver for the <code>api<\/code> guard so <code>auth:api<\/code> middleware validates OAuth2 access tokens.<\/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 (snippet)<\/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\">'passport'<\/span>,\n        <span class=\"hljs-string\">'provider'<\/span> =&gt; <span class=\"hljs-string\">'users'<\/span>,\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>Switching the <code>api<\/code> guard to the <code>passport<\/code> driver makes <code>auth:api<\/code> and Passport\u2019s scope middleware work out of the box for routes under <code>routes\/api.php<\/code>.<\/p>\n\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\/Providers\/AuthServiceProvider.php (snippet)<\/span>\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Laravel<\/span>\\<span class=\"hljs-title\">Passport<\/span>\\<span class=\"hljs-title\">Passport<\/span>;\n\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">boot<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">$this<\/span>-&gt;registerPolicies();\n\n    Passport::routes(); <span class=\"hljs-comment\">\/\/ registers \/oauth\/* routes<\/span>\n\n    <span class=\"hljs-comment\">\/\/ Optional: token expiry &amp; scopes<\/span>\n    Passport::tokensExpireIn(now()-&gt;addHours(<span class=\"hljs-number\">2<\/span>));\n    Passport::refreshTokensExpireIn(now()-&gt;addDays(<span class=\"hljs-number\">30<\/span>));\n\n    Passport::tokensCan(&#91;\n        <span class=\"hljs-string\">'read-posts'<\/span>  =&gt; <span class=\"hljs-string\">'Read posts data'<\/span>,\n        <span class=\"hljs-string\">'write-posts'<\/span> =&gt; <span class=\"hljs-string\">'Create or update posts'<\/span>,\n        <span class=\"hljs-string\">'admin'<\/span>       =&gt; <span class=\"hljs-string\">'Full administrative access'<\/span>,\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>Passport::routes()<\/code> exposes the OAuth endpoints (<code>\/oauth\/token<\/code>, <code>\/oauth\/authorize<\/code>, etc.). You can tune lifetimes and define named <em>scopes<\/em> describing which capabilities a token grants.<\/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; Issuing Tokens via Password Grant<\/strong><\/h2>\n\n\n\n<p>The <strong>Password Grant<\/strong> lets first-party apps exchange a user\u2019s email+password for an access + refresh token pair. Store the client ID\/secret in env.<\/p>\n\n\n<!-- DomainException(0): Unknown language: \"dotenv\" -->OAUTH_PASSWORD_CLIENT_ID=3\nOAUTH_PASSWORD_CLIENT_SECRET=your-password-client-secret\n\n\n<p>Find these values from the output of <code>passport:install<\/code> (or in the <code>oauth_clients<\/code> table where <code>password_client = 1<\/code>). Keep the secret confidential.<\/p>\n\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\">\/\/ app\/Http\/Controllers\/OAuth\/PasswordGrantController.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>\\<span class=\"hljs-title\">OAuth<\/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\">Http<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PasswordGrantController<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">token<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $request-&gt;validate(&#91;\n            <span class=\"hljs-string\">'username'<\/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            <span class=\"hljs-string\">'scope'<\/span>    =&gt; &#91;<span class=\"hljs-string\">'nullable'<\/span>,<span class=\"hljs-string\">'string'<\/span>], <span class=\"hljs-comment\">\/\/ e.g. \"read-posts write-posts\"<\/span>\n        ]);\n\n        $payload = &#91;\n            <span class=\"hljs-string\">'grant_type'<\/span>    =&gt; <span class=\"hljs-string\">'password'<\/span>,\n            <span class=\"hljs-string\">'client_id'<\/span>     =&gt; config(<span class=\"hljs-string\">'services.passport.password_client_id'<\/span>),\n            <span class=\"hljs-string\">'client_secret'<\/span> =&gt; config(<span class=\"hljs-string\">'services.passport.password_client_secret'<\/span>),\n            <span class=\"hljs-string\">'username'<\/span>      =&gt; $request-&gt;username,\n            <span class=\"hljs-string\">'password'<\/span>      =&gt; $request-&gt;password,\n            <span class=\"hljs-string\">'scope'<\/span>         =&gt; $request-&gt;input(<span class=\"hljs-string\">'scope'<\/span>,<span class=\"hljs-string\">''<\/span>),\n        ];\n\n        $response = Http::asForm()-&gt;post(url(<span class=\"hljs-string\">'\/oauth\/token'<\/span>), $payload);\n\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json($response-&gt;json(), $response-&gt;status());\n    }\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>This endpoint proxies your request to Passport\u2019s <code>\/oauth\/token<\/code> and returns the OAuth2 response (access token, refresh token, expires_in). Limit this to trusted first-party clients; never expose the password client secret to browsers you don\u2019t control.<\/p>\n\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\/services.php (snippet)<\/span>\n<span class=\"hljs-string\">'passport'<\/span> =&gt; &#91;\n    <span class=\"hljs-string\">'password_client_id'<\/span>     =&gt; env(<span class=\"hljs-string\">'OAUTH_PASSWORD_CLIENT_ID'<\/span>),\n    <span class=\"hljs-string\">'password_client_secret'<\/span> =&gt; env(<span class=\"hljs-string\">'OAUTH_PASSWORD_CLIENT_SECRET'<\/span>),\n],\n\n<span class=\"hljs-comment\">\/\/ routes\/api.php (snippet)<\/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\">OAuth<\/span>\\<span class=\"hljs-title\">PasswordGrantController<\/span>;\nRoute::post(<span class=\"hljs-string\">'\/oauth\/password\/token'<\/span>, &#91;PasswordGrantController::class, <span class=\"hljs-string\">'token'<\/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>Config values centralize secrets and keep controllers clean. The route exposes a first-party-only token exchange endpoint your SPA\/mobile client can call via your backend.<\/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; Client Credentials (M2M) Tokens<\/strong><\/h2>\n\n\n\n<p>For server-to-server integrations (no user), use the <strong>Client Credentials<\/strong> grant. Create a client and use its ID\/secret to fetch tokens.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan passport:client --client\n<span class=\"hljs-comment\"># copy the client_id and client_secret<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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>This creates a confidential client for machines. Those tokens won\u2019t carry a user, so your API should check capabilities accordingly (e.g., scoped to service roles only).<\/p>\n\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\">\/\/ Example: validating a client-credentials call in a controller<\/span>\n<span class=\"hljs-comment\">\/\/ $request-&gt;user() will be null; rely on token scopes\/claims<\/span>\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">m2mEndpoint<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n<\/span>{\n    <span class=\"hljs-comment\">\/\/ Gate access by scope via middleware or manual check<\/span>\n    <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'status'<\/span> =&gt; <span class=\"hljs-string\">'ok'<\/span>];\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>Because there\u2019s no user context, don\u2019t assume <code>$request-&gt;user()<\/code> exists. Protect these routes with scope middleware to limit what the machine can do.<\/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; Protecting Routes &amp; Enforcing Scopes<\/strong><\/h2>\n\n\n\n<p>Use Passport\u2019s middleware to require a valid token and specific scopes. You can combine multiple scopes for least-privilege access.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" 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\">PostApiController<\/span>;\n\nRoute::middleware(&#91;<span class=\"hljs-string\">'auth:api'<\/span>, <span class=\"hljs-string\">'scopes:read-posts'<\/span>])-&gt;get(<span class=\"hljs-string\">'\/posts'<\/span>, &#91;PostApiController::class, <span class=\"hljs-string\">'index'<\/span>]);\n\nRoute::middleware(&#91;<span class=\"hljs-string\">'auth:api'<\/span>, <span class=\"hljs-string\">'scopes:write-posts'<\/span>])-&gt;post(<span class=\"hljs-string\">'\/posts'<\/span>, &#91;PostApiController::class, <span class=\"hljs-string\">'store'<\/span>]);\n\nRoute::middleware(&#91;<span class=\"hljs-string\">'auth:api'<\/span>, <span class=\"hljs-string\">'scopes:admin'<\/span>])-&gt;delete(<span class=\"hljs-string\">'\/posts\/{post}'<\/span>, &#91;PostApiController::class, <span class=\"hljs-string\">'destroy'<\/span>]);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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>auth:api<\/code> verifies the token signature and expiration. The <code>scopes:<\/code> middleware ensures the token contains the required scope(s). Tokens without the scope will receive <code>403<\/code> responses.<\/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; Refresh Tokens &amp; Rotation<\/strong><\/h2>\n\n\n\n<p>Clients should refresh before access tokens expire. Passport supports refresh tokens out of the box via the <code>refresh_token<\/code> grant.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># Example POST to \/oauth\/token (form-encoded)<\/span>\ngrant_type=refresh_token\nclient_id=...\nclient_secret=...\nrefresh_token=... (from previous token response)<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>On success, Passport returns a new access token (and often a new refresh token). Store tokens securely (never in localStorage for sensitive apps\u2014prefer HTTP-only cookies or native secure storage on mobile).<\/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; Personal Access Tokens (Developer UX)<\/strong><\/h2>\n\n\n\n<p>Users can manually create long-lived tokens to use in tools like Postman. Add simple endpoints to mint \/ revoke them with chosen scopes.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/PersonalTokenController.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\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">PersonalTokenController<\/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\">create<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $request-&gt;validate(&#91;\n            <span class=\"hljs-string\">'name'<\/span>  =&gt; &#91;<span class=\"hljs-string\">'required'<\/span>,<span class=\"hljs-string\">'string'<\/span>,<span class=\"hljs-string\">'max:60'<\/span>],\n            <span class=\"hljs-string\">'scopes'<\/span> =&gt; &#91;<span class=\"hljs-string\">'array'<\/span>],\n            <span class=\"hljs-string\">'scopes.*'<\/span> =&gt; &#91;<span class=\"hljs-string\">'string'<\/span>],\n        ]);\n\n        $token = $request-&gt;user()-&gt;createToken(\n            $request-&gt;name,\n            $request-&gt;input(<span class=\"hljs-string\">'scopes'<\/span>, &#91;]) <span class=\"hljs-comment\">\/\/ e.g. &#91;'read-posts']<\/span>\n        );\n\n        <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'token'<\/span> =&gt; $token-&gt;accessToken];\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">revoke<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $request-&gt;user()-&gt;token()-&gt;revoke();\n        <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'status'<\/span> =&gt; <span class=\"hljs-string\">'revoked'<\/span>];\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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>createToken()<\/code> issues a personal access token for the authenticated user with the requested scopes. This is convenient for CLI scripts and power users; still apply least-privilege scopes.<\/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: OAuth Token Playground (Password Grant)<\/strong><\/h2>\n\n\n\n<p>Here\u2019s a tiny Blade UI that exchanges email\/password for an access token, then calls a protected endpoint with scopes.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;!-- resources\/views\/oauth\/playground.blade.php --&gt;\n@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n\n@section(<span class=\"hljs-string\">'content'<\/span>)\n&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span>\"&gt;\n  &lt;<span class=\"hljs-title\">h1<\/span>&gt;<span class=\"hljs-title\">OAuth<\/span> <span class=\"hljs-title\">Token<\/span> <span class=\"hljs-title\">Playground<\/span> (<span class=\"hljs-title\">Passport<\/span>)&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;\n\n  &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">row<\/span> <span class=\"hljs-title\">g<\/span>-3\"&gt;\n    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">col<\/span>-<span class=\"hljs-title\">md<\/span>-4\"&gt;&lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">email<\/span>\" <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">control<\/span>\" <span class=\"hljs-title\">placeholder<\/span>=\"<span class=\"hljs-title\">Email<\/span>\"&gt;&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">col<\/span>-<span class=\"hljs-title\">md<\/span>-4\"&gt;&lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">password<\/span>\" <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">control<\/span>\" <span class=\"hljs-title\">type<\/span>=\"<span class=\"hljs-title\">password<\/span>\" <span class=\"hljs-title\">placeholder<\/span>=\"<span class=\"hljs-title\">Password<\/span>\"&gt;&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n    &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">col<\/span>-<span class=\"hljs-title\">md<\/span>-4\"&gt;&lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">scope<\/span>\" <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">control<\/span>\" <span class=\"hljs-title\">placeholder<\/span>=\"<span class=\"hljs-title\">Scopes<\/span> (<span class=\"hljs-title\">space<\/span>-<span class=\"hljs-title\">separated<\/span>), <span class=\"hljs-title\">e<\/span>.<span class=\"hljs-title\">g<\/span>. <span class=\"hljs-title\">read<\/span>-<span class=\"hljs-title\">posts<\/span>\"&gt;&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n  &lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n  &lt;<span class=\"hljs-title\">button<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">btn<\/span> <span class=\"hljs-title\">btn<\/span>-<span class=\"hljs-title\">theme<\/span> <span class=\"hljs-title\">mt<\/span>-3\" <span class=\"hljs-title\">onclick<\/span>=\"<span class=\"hljs-title\">getToken<\/span>()\"&gt;<span class=\"hljs-title\">Get<\/span> <span class=\"hljs-title\">Token<\/span>&lt;\/<span class=\"hljs-title\">button<\/span>&gt;\n  &lt;<span class=\"hljs-title\">button<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">btn<\/span> <span class=\"hljs-title\">btn<\/span>-<span class=\"hljs-title\">secondary<\/span> <span class=\"hljs-title\">mt<\/span>-3 <span class=\"hljs-title\">ms<\/span>-2\" <span class=\"hljs-title\">onclick<\/span>=\"<span class=\"hljs-title\">callApi<\/span>()\"&gt;<span class=\"hljs-title\">Call<\/span> \/<span class=\"hljs-title\">api<\/span>\/<span class=\"hljs-title\">posts<\/span>&lt;\/<span class=\"hljs-title\">button<\/span>&gt;\n\n  &lt;<span class=\"hljs-title\">pre<\/span> <span class=\"hljs-title\">id<\/span>=\"<span class=\"hljs-title\">out<\/span>\" <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">mt<\/span>-3\"&gt;&lt;\/<span class=\"hljs-title\">pre<\/span>&gt;\n&lt;\/<span class=\"hljs-title\">div<\/span>&gt;\n\n&lt;<span class=\"hljs-title\">script<\/span> <span class=\"hljs-title\">src<\/span>=\"<span class=\"hljs-title\">https<\/span>:\/\/<span class=\"hljs-title\">cdn<\/span>.<span class=\"hljs-title\">jsdelivr<\/span>.<span class=\"hljs-title\">net<\/span>\/<span class=\"hljs-title\">npm<\/span>\/<span class=\"hljs-title\">axios<\/span>\/<span class=\"hljs-title\">dist<\/span>\/<span class=\"hljs-title\">axios<\/span>.<span class=\"hljs-title\">min<\/span>.<span class=\"hljs-title\">js<\/span>\"&gt;&lt;\/<span class=\"hljs-title\">script<\/span>&gt;\n&lt;<span class=\"hljs-title\">script<\/span>&gt;\n<span class=\"hljs-title\">let<\/span> <span class=\"hljs-title\">token<\/span> = <span class=\"hljs-title\">null<\/span>;\n\n<span class=\"hljs-title\">function<\/span> <span class=\"hljs-title\">getToken<\/span>() <\/span>{\n  axios.post(<span class=\"hljs-string\">'\/api\/oauth\/password\/token'<\/span>, {\n    username: document.getElementById(<span class=\"hljs-string\">'email'<\/span>).value,\n    password: document.getElementById(<span class=\"hljs-string\">'password'<\/span>).value,\n    scope: document.getElementById(<span class=\"hljs-string\">'scope'<\/span>).value\n  }).then(res =&gt; {\n    token = res.data.access_token;\n    document.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = JSON.stringify(res.data, <span class=\"hljs-keyword\">null<\/span>, <span class=\"hljs-number\">2<\/span>);\n  }).<span class=\"hljs-keyword\">catch<\/span>(err =&gt; {\n    document.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = err.response ? JSON.stringify(err.response.data, <span class=\"hljs-keyword\">null<\/span>, <span class=\"hljs-number\">2<\/span>) : err;\n  });\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">callApi<\/span><span class=\"hljs-params\">()<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (!token) <span class=\"hljs-keyword\">return<\/span> alert(<span class=\"hljs-string\">'Get a token first'<\/span>);\n  axios.get(<span class=\"hljs-string\">'\/api\/posts'<\/span>, { headers: { Authorization: `Bearer ${token}` }})\n    .then(res =&gt; document.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = JSON.stringify(res.data, <span class=\"hljs-keyword\">null<\/span>, <span class=\"hljs-number\">2<\/span>))\n    .<span class=\"hljs-keyword\">catch<\/span>(err =&gt; document.getElementById(<span class=\"hljs-string\">'out'<\/span>).textContent = err.response ? JSON.stringify(err.response.data, <span class=\"hljs-keyword\">null<\/span>, <span class=\"hljs-number\">2<\/span>) : err);\n}\n&lt;\/script&gt;\n@endsection<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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 page first hits your password-grant proxy to fetch an access token (optionally with scopes), then uses it to call a scoped, protected API. This is perfect for quick verification without Postman.<\/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>Passport equips Laravel with a full OAuth2 server: password and client-credentials grants, refresh tokens, personal access tokens, and scope-based authorization. You set up guard integration, issued tokens securely, enforced scopes on routes, and built a tiny UI to test flows. Choose Passport when you need interoperable OAuth2 with multiple client types and granular permissions; for simpler first-party apps, Sanctum may suffice.<\/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\/how-to-add-jwt-authentication-to-laravel-apis\">How to Add JWT Authentication to Laravel APIs<\/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 Laravel &amp; Sanctum<\/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<\/ul>\n\n","protected":false},"excerpt":{"rendered":"<p>Using Laravel Passport for Advanced API Authentication Laravel Passport brings a full OAuth2 server to your Laravel app\u2014great when you need first-class features beyond simple token auth: password grants, client credentials (machine-to-machine), refresh tokens, and scopes for fine-grained permissions. In this guide you\u2019ll install Passport, wire up guards, issue tokens via multiple grants, enforce scopes, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":360,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[25,56,57],"class_list":["post-356","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-api","tag-oauth2","tag-passport"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/356","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=356"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/356\/revisions"}],"predecessor-version":[{"id":359,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/356\/revisions\/359"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/360"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=356"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=356"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=356"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}