{"id":174,"date":"2025-08-26T14:41:51","date_gmt":"2025-08-26T14:41:51","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=174"},"modified":"2025-08-26T14:41:53","modified_gmt":"2025-08-26T14:41:53","slug":"best-practices-for-storing-api-keys-securely-in-laravel","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/best-practices-for-storing-api-keys-securely-in-laravel\/","title":{"rendered":"Best Practices for Storing API Keys Securely in Laravel"},"content":{"rendered":"\n<p>Hardcoding secrets in code or committing them to Git is a fast way to leak credentials. In this guide, you\u2019ll learn <strong>how to store API keys securely in Laravel 12<\/strong> using environment variables, configuration, optional encrypted database storage with an admin UI, and (optionally) cloud secret managers. Every step includes code and explanations so beginners can implement it safely.<\/p>\n\n\n\n<p>By the end, you\u2019ll be able to: keep secrets out of source control, load them via <code>config()<\/code> (not <code>env()<\/code>) in app code, optionally encrypt them at rest in your database with a simple settings UI, and understand when to adopt a managed secret store in production.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Core Principles (Read This First)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Never commit secrets to Git<\/strong> (including private repos). Use <code>.env<\/code> or secret managers.<\/li>\n<li><strong>Read secrets via <code>config()<\/code><\/strong> in application code. Avoid calling <code>env()<\/code> outside config files.<\/li>\n<li><strong>Limit exposure<\/strong>: only inject the secrets you actually need in each environment.<\/li>\n<li><strong>Rotate regularly<\/strong>, log access, and <strong>never print secrets in logs<\/strong>.<\/li>\n<\/ul>\n\n\n\n<p><\/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; Put secrets in <code>.env<\/code>, read them from <code>config\/services.php<\/code><\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\"># .env<\/span>\nMAILGUN_API_KEY=your-mailgun-key\nSTRIPE_SECRET=sk_live_xxx\nTHIRD_PARTY_MAPS_KEY=maps-xxx<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><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>Expose them to your app via configuration so you can safely call <code>config()<\/code> everywhere else:<\/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 shcb-code-table shcb-line-numbers shcb-wrap-lines\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ config\/services.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">return<\/span> &#91;\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-string\">'mailgun'<\/span> =&gt; &#91;\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">'key'<\/span> =&gt; env(<span class=\"hljs-string\">'MAILGUN_API_KEY'<\/span>),\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-string\">'stripe'<\/span> =&gt; &#91;\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">'secret'<\/span> =&gt; env(<span class=\"hljs-string\">'STRIPE_SECRET'<\/span>),\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-string\">'maps'<\/span> =&gt; &#91;\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-string\">'key'<\/span> =&gt; env(<span class=\"hljs-string\">'THIRD_PARTY_MAPS_KEY'<\/span>),\n<\/span><\/span><span class='shcb-loc'><span>    ],\n<\/span><\/span><span class='shcb-loc'><span>];\n<\/span><\/span><\/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>In your controllers\/services, reference <code>config('services.mailgun.key')<\/code> (not <code>env()<\/code>). This keeps production fast and predictable when you run <code>php artisan config:cache<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan config:cache<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><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><\/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; Use secrets in code (safely)<\/strong><\/h2>\n\n\n\n<p>Fetch secrets via <code>config()<\/code> and never log or echo them:<\/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\">\/\/ app\/Services\/EmailService.php<\/span>\n<span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Services<\/span>;\n\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\">EmailService<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">sendTransactional<\/span><span class=\"hljs-params\">($to, $subject, $html)<\/span>\n    <\/span>{\n        $key = config(<span class=\"hljs-string\">'services.mailgun.key'<\/span>); <span class=\"hljs-comment\">\/\/ do not use env() here<\/span>\n\n        <span class=\"hljs-keyword\">return<\/span> Http::withToken($key)\n            -&gt;post(<span class=\"hljs-string\">'https:\/\/api.mailgun.net\/v3\/your-domain\/messages'<\/span>, &#91;\n                <span class=\"hljs-string\">'to'<\/span> =&gt; $to,\n                <span class=\"hljs-string\">'subject'<\/span> =&gt; $subject,\n                <span class=\"hljs-string\">'html'<\/span> =&gt; $html,\n            ]);\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>Keep secrets out of debug pages and logs: don\u2019t dump <code>$key<\/code>, don\u2019t include it in exceptions.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Optional: Encrypt secrets at rest in your database (with an Admin UI)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Why do this?<\/strong> If non-technical admins need to update API keys without SSH access, store them in a small <code>app_settings<\/code> table <em>encrypted at rest<\/em>, exposed via a protected settings page. This approach complements (not replaces) <code>.env<\/code> for certain keys.<\/p>\n<\/blockquote>\n\n\n\n<p>Create the table:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:migration create_app_settings_table<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><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<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 shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ database\/migrations\/xxxx_xx_xx_create_app_settings_table.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Migrations<\/span>\\<span class=\"hljs-title\">Migration<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Schema<\/span>\\<span class=\"hljs-title\">Blueprint<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><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\">Schema<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Migration<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">up<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    Schema::create(<span class=\"hljs-string\">'app_settings'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>      $table-&gt;id();\n<\/span><\/span><span class='shcb-loc'><span>      $table-&gt;string(<span class=\"hljs-string\">'key'<\/span>)-&gt;unique();\n<\/span><\/span><span class='shcb-loc'><span>      $table-&gt;text(<span class=\"hljs-string\">'value'<\/span>);   <span class=\"hljs-comment\">\/\/ encrypted blob<\/span>\n<\/span><\/span><span class='shcb-loc'><span>      $table-&gt;timestamps();\n<\/span><\/span><span class='shcb-loc'><span>    });\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>  <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">down<\/span><span class=\"hljs-params\">()<\/span>: <span class=\"hljs-title\">void<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    Schema::dropIfExists(<span class=\"hljs-string\">'app_settings'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  }\n<\/span><\/span><span class='shcb-loc'><span>};\n<\/span><\/span><\/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>Model with transparent encryption\/decryption:<\/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 shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ app\/Models\/AppSetting.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Database<\/span>\\<span class=\"hljs-title\">Eloquent<\/span>\\<span class=\"hljs-title\">Model<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">AppSetting<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Model<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">protected<\/span> $fillable = &#91;<span class=\"hljs-string\">'key'<\/span>,<span class=\"hljs-string\">'value'<\/span>];\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setValueAttribute<\/span><span class=\"hljs-params\">($val)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">$this<\/span>-&gt;attributes&#91;<span class=\"hljs-string\">'value'<\/span>] = encrypt($val);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getValueAttribute<\/span><span class=\"hljs-params\">($val)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> decrypt($val);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/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>Routes + controller (lock behind Admin role):<\/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 shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ routes\/web.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><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\">Admin<\/span>\\<span class=\"hljs-title\">SettingsController<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>Route::middleware(&#91;<span class=\"hljs-string\">'auth'<\/span>,<span class=\"hljs-string\">'role:Admin'<\/span>])-&gt;group(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>  Route::get(<span class=\"hljs-string\">'\/admin\/settings'<\/span>, &#91;SettingsController::class,<span class=\"hljs-string\">'edit'<\/span>])-&gt;name(<span class=\"hljs-string\">'admin.settings.edit'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>  Route::post(<span class=\"hljs-string\">'\/admin\/settings'<\/span>, &#91;SettingsController::class,<span class=\"hljs-string\">'update'<\/span>])-&gt;name(<span class=\"hljs-string\">'admin.settings.update'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>});\n<\/span><\/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<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/Admin\/SettingsController.php<\/span>\n<\/span><\/span><span class='shcb-loc'><span><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\">Admin<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><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\">Controller<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">AppSetting<\/span>;\n<\/span><\/span><span class='shcb-loc'><span><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><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">SettingsController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><\/span>{\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">edit<\/span><span class=\"hljs-params\">()<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        $mailgun = AppSetting::firstWhere(<span class=\"hljs-string\">'key'<\/span>,<span class=\"hljs-string\">'mailgun_key'<\/span>)?-&gt;value;\n<\/span><\/span><span class='shcb-loc'><span>        $stripe  = AppSetting::firstWhere(<span class=\"hljs-string\">'key'<\/span>,<span class=\"hljs-string\">'stripe_secret'<\/span>)?-&gt;value;\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> view(<span class=\"hljs-string\">'admin.settings'<\/span>, compact(<span class=\"hljs-string\">'mailgun'<\/span>,<span class=\"hljs-string\">'stripe'<\/span>));\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">update<\/span><span class=\"hljs-params\">(Request $r)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-function\">    <\/span>{\n<\/span><\/span><span class='shcb-loc'><span>        $data = $r-&gt;validate(&#91;\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'mailgun_key'<\/span> =&gt; <span class=\"hljs-string\">'nullable|string'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>            <span class=\"hljs-string\">'stripe_secret'<\/span> =&gt; <span class=\"hljs-string\">'nullable|string'<\/span>,\n<\/span><\/span><span class='shcb-loc'><span>        ]);\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span>(<span class=\"hljs-keyword\">isset<\/span>($data&#91;<span class=\"hljs-string\">'mailgun_key'<\/span>])){\n<\/span><\/span><span class='shcb-loc'><span>            AppSetting::updateOrCreate(&#91;<span class=\"hljs-string\">'key'<\/span> =&gt; <span class=\"hljs-string\">'mailgun_key'<\/span>], &#91;<span class=\"hljs-string\">'value'<\/span> =&gt; $data&#91;<span class=\"hljs-string\">'mailgun_key'<\/span>]]);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">if<\/span>(<span class=\"hljs-keyword\">isset<\/span>($data&#91;<span class=\"hljs-string\">'stripe_secret'<\/span>])){\n<\/span><\/span><span class='shcb-loc'><span>            AppSetting::updateOrCreate(&#91;<span class=\"hljs-string\">'key'<\/span> =&gt; <span class=\"hljs-string\">'stripe_secret'<\/span>], &#91;<span class=\"hljs-string\">'value'<\/span> =&gt; $data&#91;<span class=\"hljs-string\">'stripe_secret'<\/span>]]);\n<\/span><\/span><span class='shcb-loc'><span>        }\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>        <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>,<span class=\"hljs-string\">'Settings updated.'<\/span>);\n<\/span><\/span><span class='shcb-loc'><span>    }\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><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>Minimal UI:<\/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 shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span>&lt;!-- resources\/views\/admin\/settings.blade.php --&gt;\n<\/span><\/span><span class='shcb-loc'><span>@extends(<span class=\"hljs-string\">'layouts.app'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>@section(<span class=\"hljs-string\">'content'<\/span>)\n<\/span><\/span><span class='shcb-loc'><span>&lt;div <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">container<\/span> <span class=\"hljs-title\">py<\/span>-4\" <span class=\"hljs-title\">style<\/span>=\"<span class=\"hljs-title\">max<\/span>-<span class=\"hljs-title\">width<\/span>:680<span class=\"hljs-title\">px<\/span>\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  &lt;<span class=\"hljs-title\">h1<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">h5<\/span> <span class=\"hljs-title\">mb<\/span>-3\"&gt;<span class=\"hljs-title\">Secure<\/span> <span class=\"hljs-title\">Settings<\/span>&lt;\/<span class=\"hljs-title\">h1<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">  @<span class=\"hljs-title\">if<\/span> (<span class=\"hljs-title\">session<\/span>('<span class=\"hljs-title\">status<\/span>')) &lt;<span class=\"hljs-title\">div<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">alert<\/span> <span class=\"hljs-title\">alert<\/span>-<span class=\"hljs-title\">success<\/span>\"&gt;<\/span>{{ session(<span class=\"hljs-string\">'status'<\/span>) }}&lt;\/div&gt; @<span class=\"hljs-keyword\">endif<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>  &lt;form method=<span class=\"hljs-string\">\"POST\"<\/span> action=<span class=\"hljs-string\">\"{{ route('admin.settings.update') }}\"<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span>=\"<span class=\"hljs-title\">card<\/span> <span class=\"hljs-title\">card<\/span>-<span class=\"hljs-title\">body<\/span>\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    @<span class=\"hljs-title\">csrf<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">label<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">label<\/span>\"&gt;<span class=\"hljs-title\">Mailgun<\/span> <span class=\"hljs-title\">API<\/span> <span class=\"hljs-title\">Key<\/span>&lt;\/<span class=\"hljs-title\">label<\/span>&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\">    &lt;<span class=\"hljs-title\">input<\/span> <span class=\"hljs-title\">class<\/span>=\"<span class=\"hljs-title\">form<\/span>-<span class=\"hljs-title\">control<\/span> <span class=\"hljs-title\">mb<\/span>-3\" <span class=\"hljs-title\">name<\/span>=\"<span class=\"hljs-title\">mailgun_key<\/span>\" <span class=\"hljs-title\">value<\/span>=\"<\/span>{{ old(<span class=\"hljs-string\">'mailgun_key'<\/span>, $mailgun) }}<span class=\"hljs-string\">\" autocomplete=\"<\/span>off<span class=\"hljs-string\">\"&gt;<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;label class=\"<\/span>form-label<span class=\"hljs-string\">\"&gt;Stripe Secret&lt;\/label&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;input class=\"<\/span>form-control mb<span class=\"hljs-number\">-3<\/span><span class=\"hljs-string\">\" name=\"<\/span>stripe_secret<span class=\"hljs-string\">\" value=\"<\/span>{{ old(<span class=\"hljs-string\">'stripe_secret'<\/span>, $stripe) }}<span class=\"hljs-string\">\" autocomplete=\"<\/span>off<span class=\"hljs-string\">\"&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;button class=\"<\/span>btn btn-primary<span class=\"hljs-string\">\"&gt;Save&lt;\/button&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">    &lt;p class=\"<\/span>text-muted small mt<span class=\"hljs-number\">-2<\/span> mb<span class=\"hljs-number\">-0<\/span><span class=\"hljs-string\">\"&gt;Values are encrypted at rest.&lt;\/p&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">  &lt;\/form&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">&lt;\/div&gt;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-class\"><span class=\"hljs-string\">@endsection<\/span><\/span>\n<\/span><\/span><\/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><strong>How to use these settings in code?<\/strong> Read them (decrypted) via the model or a small cache:<\/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\">$mailgun = optional(\\App\\Models\\AppSetting::firstWhere(<span class=\"hljs-string\">'key'<\/span>,<span class=\"hljs-string\">'mailgun_key'<\/span>))?-&gt;value;\n$stripeSecret = optional(\\App\\Models\\AppSetting::firstWhere(<span class=\"hljs-string\">'key'<\/span>,<span class=\"hljs-string\">'stripe_secret'<\/span>))?-&gt;value;<\/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><strong>Security notes:<\/strong> protect the route with Admin-only access, consider activity logs, and never display secrets in plain text to non-privileged users.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Optional: Use a managed Secrets Manager in production (how-to)<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>AWS Secrets Manager<\/strong>: store secrets by name; in your deploy pipeline\/instance, fetch them and write to <code>.env<\/code> (or set as environment variables). Rotate via AWS console\/policies.<\/li>\n<li><strong>GCP Secret Manager<\/strong>: grant your service account access; at boot, fetch and inject into env.<\/li>\n<li><strong>Azure Key Vault<\/strong>: grant your app\u2019s managed identity access; resolve secrets at startup.<\/li>\n<\/ul>\n\n\n\n<p><strong>Implementation sketch (AWS CLI during deploy):<\/strong><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># Retrieve and write to .env during CI\/CD or instance boot<\/span>\nMAILGUN_API_KEY=$(aws secretsmanager get-secret-value --secret-id prod\/mailgun --query <span class=\"hljs-string\">'SecretString'<\/span> --output text)\n<span class=\"hljs-built_in\">echo<\/span> <span class=\"hljs-string\">\"MAILGUN_API_KEY=<span class=\"hljs-variable\">$MAILGUN_API_KEY<\/span>\"<\/span> &gt;&gt; .env\n\n<span class=\"hljs-comment\"># Then cache config for performance<\/span>\nphp artisan config:cache<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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 keeps secrets outside the repo and centralizes rotation. Your app still reads from <code>config()<\/code> like normal.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; Common mistakes to avoid<\/strong><\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Calling <code>env()<\/code> in controllers\/services. Use <code>config()<\/code> so cached config works.<\/li>\n<li>Leaving secrets in exception messages or debug logs.<\/li>\n<li>Committing <code>.env<\/code>, screenshots of keys, or curl commands with secrets to Git.<\/li>\n<li>Not rotating keys after breaches or role changes.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<p>You learned how to store API keys securely in Laravel 12: keep them in <code>.env<\/code>, read them through <code>config()<\/code>, cache configuration in production, and optionally provide an encrypted <strong>Admin Settings<\/strong> UI for select keys. For larger teams or stricter environments, integrate a <strong>managed secrets manager<\/strong> and inject values at deploy time. Most importantly: never log secrets, never commit them, and rotate regularly.<\/p>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">What\u2019s Next<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/blog\/how-to-prevent-csrf-xss-and-sql-injection-in-laravel-apps\">How to Prevent CSRF, XSS, and SQL Injection in Laravel Apps<\/a> \u2014 defense-in-depth for common web attacks.<\/li>\n<li><a href=\"\/blog\/securing-laravel-apis-with-sanctum-complete-guide\">Securing Laravel APIs with Sanctum: Complete Guide<\/a> \u2014 protect SPAs and mobile clients.<\/li>\n<li><a href=\"\/blog\/how-to-expire-user-sessions-automatically-in-laravel\">How to Expire User Sessions Automatically in Laravel<\/a> \u2014 reduce session risk.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n\n\n<div style=\"height:100px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Hardcoding secrets in code or committing them to Git is a fast way to leak credentials. In this guide, you\u2019ll learn how to store API keys securely in Laravel 12 using environment variables, configuration, optional encrypted database storage with an admin UI, and (optionally) cloud secret managers. Every step includes code and explanations so beginners [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":179,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[22],"class_list":["post-174","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-security"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/174","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=174"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/174\/revisions"}],"predecessor-version":[{"id":178,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/174\/revisions\/178"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/179"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=174"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=174"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}