{"id":205,"date":"2025-08-27T13:02:45","date_gmt":"2025-08-27T13:02:45","guid":{"rendered":"https:\/\/1v0.net\/blog\/?p=205"},"modified":"2025-08-27T13:02:48","modified_gmt":"2025-08-27T13:02:48","slug":"how-to-add-github-login-to-laravel-12-with-socialite","status":"publish","type":"post","link":"https:\/\/1v0.net\/blog\/how-to-add-github-login-to-laravel-12-with-socialite\/","title":{"rendered":"How to Add GitHub Login to Laravel 12 with Socialite"},"content":{"rendered":"\n<p>When building applications for developers, a top UX win is to let users sign in with their existing <strong>GitHub<\/strong> account. It reduces friction, avoids extra passwords, and signals trust. In this guide, you\u2019ll add <strong>GitHub Login to a Laravel 12 app using Socialite<\/strong> \u2014 with clear steps, fully explained code, and a simple UI button.<\/p>\n\n\n\n<p>We\u2019ll install Socialite, create a GitHub OAuth app, wire up environment variables and services config, add routes and a controller, update the users table, and place a polished \u201cLogin with GitHub\u201d button. Optional sections cover requesting private emails, linking\/unlinking GitHub from a user profile, and common error fixes.<\/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\n<h2 class=\"wp-block-heading\"><strong>1 &#8211; Install Socialite<\/strong><\/h2>\n\n\n\n<p>Socialite is Laravel\u2019s official OAuth client for providers such as GitHub, Google, and Facebook.<\/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\/socialite<\/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>Laravel 12 uses package auto-discovery, so you don\u2019t normally need to register anything. If auto-discovery is disabled, add the provider and alias manually:<\/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\/app.php (only if NOT auto-discovered)<\/span>\n<span class=\"hljs-string\">'providers'<\/span> =&gt; &#91;\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    Laravel\\Socialite\\SocialiteServiceProvider::class,\n],\n\n<span class=\"hljs-string\">'aliases'<\/span> =&gt; &#91;\n    <span class=\"hljs-comment\">\/\/ ...<\/span>\n    <span class=\"hljs-string\">'Socialite'<\/span> =&gt; Laravel\\Socialite\\Facades\\Socialite::class,\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><\/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>2 &#8211; Create GitHub OAuth App<\/strong><\/h2>\n\n\n\n<p>Register your app with GitHub to obtain a Client ID and Client Secret:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open <a href=\"https:\/\/github.com\/settings\/developers\" target=\"_blank\" rel=\"noopener\">GitHub &rarr; Settings &rarr; Developer settings &rarr; OAuth Apps<\/a>.<\/li>\n<li>Click <strong>New OAuth App<\/strong>.<\/li>\n<li>Set:\n<ul>\n<li><strong>Homepage URL:<\/strong> e.g. <code>https:\/\/your-domain.com<\/code><\/li>\n<li><strong>Authorization callback URL:<\/strong> <code>https:\/\/your-domain.com\/auth\/github\/callback<\/code><\/li>\n<\/ul>\n<\/li>\n<li>Save and copy your <strong>Client ID<\/strong> and <strong>Client Secret<\/strong>.<\/li>\n<\/ol>\n\n\n\n<p>For local development, use:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">http:<span class=\"hljs-comment\">\/\/localhost:8000\/auth\/github\/callback<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Callback URL must match exactly (protocol, domain, path) or GitHub will reject the redirect.<\/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\n<h2 class=\"wp-block-heading\"><strong>3 &#8211; Configure Laravel (.env &amp; services.php)<\/strong><\/h2>\n\n\n\n<p>Add credentials to your environment file so keys never live in source control:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\"><span class=\"hljs-comment\"># .env<\/span>\nGITHUB_CLIENT_ID=your_client_id_here\nGITHUB_CLIENT_SECRET=your_client_secret_here\nGITHUB_REDIRECT_URI=<span class=\"hljs-variable\">${APP_URL}<\/span>\/auth\/github\/callback\n\n<span class=\"hljs-comment\"># Ensure these are correct for sessions\/cookies:<\/span>\nAPP_URL=http:\/\/localhost:8000\n<span class=\"hljs-comment\"># SESSION_DOMAIN=.your-domain.com   # if you use subdomains<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><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>Tell Laravel where to read these values in <code>config\/services.php<\/code>:<\/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\/services.php<\/span>\n<span class=\"hljs-string\">'github'<\/span> =&gt; &#91;\n    <span class=\"hljs-string\">'client_id'<\/span>     =&gt; env(<span class=\"hljs-string\">'GITHUB_CLIENT_ID'<\/span>),\n    <span class=\"hljs-string\">'client_secret'<\/span> =&gt; env(<span class=\"hljs-string\">'GITHUB_CLIENT_SECRET'<\/span>),\n    <span class=\"hljs-string\">'redirect'<\/span>      =&gt; env(<span class=\"hljs-string\">'GITHUB_REDIRECT_URI'<\/span>),\n],<\/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>Laravel will now use these secrets at runtime via <code>config('services.github.*')<\/code>.<\/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\n<h2 class=\"wp-block-heading\"><strong>4 &#8211; Routes<\/strong><\/h2>\n\n\n\n<p>We\u2019ll add two guest-only routes: one to redirect the user to GitHub; one to handle the callback:<\/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\">\/\/ routes\/web.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\">Auth<\/span>\\<span class=\"hljs-title\">GitHubController<\/span>;\n\nRoute::middleware(<span class=\"hljs-string\">'guest'<\/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\">'\/auth\/github\/redirect'<\/span>, &#91;GitHubController::class, <span class=\"hljs-string\">'redirect'<\/span>])\n        -&gt;name(<span class=\"hljs-string\">'github.redirect'<\/span>);\n\n    Route::get(<span class=\"hljs-string\">'\/auth\/github\/callback'<\/span>, &#91;GitHubController::class, <span class=\"hljs-string\">'callback'<\/span>])\n        -&gt;name(<span class=\"hljs-string\">'github.callback'<\/span>);\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><code>\/auth\/github\/redirect<\/code> sends users to GitHub\u2019s consent screen. After approval, GitHub redirects them to <code>\/auth\/github\/callback<\/code> with a code Socialite exchanges for profile data.<\/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\n<h2 class=\"wp-block-heading\"><strong>5 &#8211; Users Table: store github_id (and avatar)<\/strong><\/h2>\n\n\n\n<p>We\u2019ll store the GitHub user ID and optional avatar URL so returning users can log in seamlessly.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:migration add_github_columns_to_users_table --table=users<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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-8\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ database\/migrations\/xxxx_xx_xx_xxxxxx_add_github_columns_to_users_table.php<\/span>\n<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 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 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\n<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 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        Schema::table(<span class=\"hljs-string\">'users'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;string(<span class=\"hljs-string\">'github_id'<\/span>)-&gt;nullable()-&gt;index();\n            $table-&gt;string(<span class=\"hljs-string\">'avatar'<\/span>)-&gt;nullable();\n        });\n    }\n\n    <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        Schema::table(<span class=\"hljs-string\">'users'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">(Blueprint $table)<\/span> <\/span>{\n            $table-&gt;dropColumn(&#91;<span class=\"hljs-string\">'github_id'<\/span>,<span class=\"hljs-string\">'avatar'<\/span>]);\n        });\n    }\n};<\/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=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan migrate<\/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>This keeps a durable link between your local user and their GitHub identity.<\/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\n<h2 class=\"wp-block-heading\"><strong>6 &#8211; GitHub Controller<\/strong><\/h2>\n\n\n\n<p>Create a controller to handle the redirect and callback. It logs in existing users or creates a new account as needed.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"Bash\" data-shcb-language-slug=\"bash\"><span><code class=\"hljs language-bash\">php artisan make:controller Auth\/GitHubController<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><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-11\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ app\/Http\/Controllers\/Auth\/GitHubController.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\">Auth<\/span>;\n\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\">Controller<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Models<\/span>\\<span class=\"hljs-title\">User<\/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<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Str<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Laravel<\/span>\\<span class=\"hljs-title\">Socialite<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Socialite<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">GitHubController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-comment\">\/\/ Step 1: send user to GitHub<\/span>\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">redirect<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        <span class=\"hljs-comment\">\/\/ Request public profile + email (private emails require 'user:email' scope \u2014 see optional section)<\/span>\n        <span class=\"hljs-keyword\">return<\/span> Socialite::driver(<span class=\"hljs-string\">'github'<\/span>)-&gt;redirect();\n    }\n\n    <span class=\"hljs-comment\">\/\/ Step 2: handle callback<\/span>\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">callback<\/span><span class=\"hljs-params\">()<\/span>\n    <\/span>{\n        $git = Socialite::driver(<span class=\"hljs-string\">'github'<\/span>)-&gt;user();\n\n        $email   = $git-&gt;getEmail();            <span class=\"hljs-comment\">\/\/ may be null if hidden<\/span>\n        $gitId   = $git-&gt;getId();\n        $name    = $git-&gt;getName() ?: $git-&gt;getNickname() ?: <span class=\"hljs-string\">'GitHub User'<\/span>;\n        $avatar  = $git-&gt;getAvatar();\n\n        <span class=\"hljs-comment\">\/\/ A) Already linked<\/span>\n        $user = User::where(<span class=\"hljs-string\">'github_id'<\/span>, $gitId)-&gt;first();\n\n        <span class=\"hljs-comment\">\/\/ B) Not linked but email matches an existing account \u2014 link it<\/span>\n        <span class=\"hljs-keyword\">if<\/span> (! $user &amp;&amp; $email) {\n            $user = User::where(<span class=\"hljs-string\">'email'<\/span>, $email)-&gt;first();\n            <span class=\"hljs-keyword\">if<\/span> ($user) {\n                $user-&gt;forceFill(&#91;\n                    <span class=\"hljs-string\">'github_id'<\/span> =&gt; $gitId,\n                    <span class=\"hljs-string\">'avatar'<\/span>    =&gt; $user-&gt;avatar ?: $avatar,\n                ])-&gt;save();\n            }\n        }\n\n        <span class=\"hljs-comment\">\/\/ C) No match \u2014 create a new local account<\/span>\n        <span class=\"hljs-keyword\">if<\/span> (! $user) {\n            $user = User::create(&#91;\n                <span class=\"hljs-string\">'name'<\/span>       =&gt; $name,\n                <span class=\"hljs-string\">'email'<\/span>      =&gt; $email, <span class=\"hljs-comment\">\/\/ can be null if GitHub doesn't provide it<\/span>\n                <span class=\"hljs-string\">'password'<\/span>   =&gt; bcrypt(Str::random(<span class=\"hljs-number\">32<\/span>)),\n                <span class=\"hljs-string\">'github_id'<\/span>  =&gt; $gitId,\n                <span class=\"hljs-string\">'avatar'<\/span>     =&gt; $avatar,\n            ]);\n        }\n\n        Auth::login($user, remember: <span class=\"hljs-keyword\">true<\/span>);\n\n        <span class=\"hljs-keyword\">return<\/span> redirect()-&gt;intended(<span class=\"hljs-string\">'\/'<\/span>);\n    }\n}<\/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>This flow supports three scenarios: previously linked user, same-email user (auto-link), or brand-new user. You can swap \u201cauto-link\u201d with a confirmation screen if your policy requires explicit consent.<\/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\n<h2 class=\"wp-block-heading\"><strong>7 &#8211; UI: \u201cLogin with GitHub\u201d Button<\/strong><\/h2>\n\n\n\n<p>Add a clear call-to-action on your login page:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"{{ route('github.redirect') }}\"<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"btn btn-dark w-100\"<\/span>&gt;<\/span>\n  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">i<\/span> <span class=\"hljs-attr\">class<\/span>=<span class=\"hljs-string\">\"bi bi-github me-2\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">i<\/span>&gt;<\/span> Login with GitHub\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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>This simply hits the redirect route, takes the user to GitHub, and returns them to your app signed in.<\/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\n<h2 class=\"wp-block-heading\"><strong>8 &#8211; Optional: Request private emails (user:email scope)<\/strong><\/h2>\n\n\n\n<p>Some users hide their email on GitHub. To retrieve private emails, request the <code>user:email<\/code> scope and use <code>stateless()<\/code> only if you understand the session tradeoffs.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ Redirect with scope<\/span>\n<span class=\"hljs-keyword\">return<\/span> Socialite::driver(<span class=\"hljs-string\">'github'<\/span>)\n    -&gt;scopes(&#91;<span class=\"hljs-string\">'user:email'<\/span>])\n    -&gt;redirect();\n\n<span class=\"hljs-comment\">\/\/ Later in callback, iterate emails if primary is missing:<\/span>\n$git = Socialite::driver(<span class=\"hljs-string\">'github'<\/span>)-&gt;user();\n$email = $git-&gt;getEmail();\n\n<span class=\"hljs-keyword\">if<\/span> (! $email &amp;&amp; <span class=\"hljs-keyword\">isset<\/span>($git-&gt;user&#91;<span class=\"hljs-string\">'email'<\/span>])) {\n    $email = $git-&gt;user&#91;<span class=\"hljs-string\">'email'<\/span>];\n}\n\n<span class=\"hljs-comment\">\/\/ Some SDKs expose emails array under $git-&gt;user&#91;'emails']<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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>Always handle the case where email remains <code>null<\/code> (e.g., prompt the user to add an email after first login).<\/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\n<h2 class=\"wp-block-heading\"><strong>9 &#8211; Optional: Link\/Unlink GitHub from Account Settings<\/strong><\/h2>\n\n\n\n<p>Let signed-in users connect or disconnect GitHub later. Protect routes with <code>auth<\/code> middleware and attach the <code>github_id<\/code> to the current user.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">\/\/ routes\/web.php (link &amp; disconnect)<\/span>\nRoute::middleware(<span class=\"hljs-string\">'auth'<\/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\">'\/account\/github\/connect'<\/span>, &#91;GitHubController::class, <span class=\"hljs-string\">'redirect'<\/span>])\n        -&gt;name(<span class=\"hljs-string\">'github.connect'<\/span>);\n\n    Route::post(<span class=\"hljs-string\">'\/account\/github\/disconnect'<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-params\">()<\/span> <\/span>{\n        auth()-&gt;user()-&gt;forceFill(&#91;<span class=\"hljs-string\">'github_id'<\/span> =&gt; <span class=\"hljs-keyword\">null<\/span>])-&gt;save();\n        <span class=\"hljs-keyword\">return<\/span> back()-&gt;with(<span class=\"hljs-string\">'status'<\/span>, <span class=\"hljs-string\">'GitHub disconnected.'<\/span>);\n    })-&gt;name(<span class=\"hljs-string\">'github.disconnect'<\/span>);\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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 the callback, if <code>auth()-&gt;check()<\/code> is true, attach the <code>github_id<\/code> to the current user instead of logging in a different account. This prevents account takeovers.<\/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\n<h2 class=\"wp-block-heading\"><strong>10 &#8211; Common Issues &amp; Fixes<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Redirect URI mismatch:<\/strong> Ensure the callback in GitHub settings equals <code>GITHUB_REDIRECT_URI<\/code> exactly (protocol\/host\/path).<\/li>\n<li><strong>Invalid state \/ CSRF:<\/strong> Don\u2019t use <code>stateless()<\/code> unless building a pure API. Make sure sessions\/cookies work (correct <code>APP_URL<\/code>, <code>SESSION_DOMAIN<\/code>).<\/li>\n<li><strong>Email is null:<\/strong> Request <code>user:email<\/code> scope and still handle <code>null<\/code> gracefully by prompting for an email post-login.<\/li>\n<li><strong>Subdomain login issues:<\/strong> Use a shared cookie domain, e.g., <code>SESSION_DOMAIN=.your-domain.com<\/code>.<\/li>\n<\/ul>\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\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>You added a smooth <strong>\u201cLogin with GitHub\u201d<\/strong> to Laravel 12 using Socialite: installed the package, created OAuth credentials, configured services, added routes and a controller, updated your users table, and shipped a clean UI button. Optional sections showed how to request private emails and how to let users link\/unlink GitHub from their profile \u2014 production-friendly polish your users will appreciate.<\/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\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-google-login-laravel-12\">How to Add Google Login to a Laravel 12 App with Socialite<\/a> \u2014 offer multiple social providers.<\/li>\n<li><a href=\"\/blog\/laravel-spatie-permissions-step-by-step-installation-setup\">Laravel Spatie Permissions: Step-by-Step Setup<\/a> \u2014 assign roles\/permissions after login.<\/li>\n<li><a href=\"\/blog\/implementing-passwordless-authentication-in-laravel-12\">Implementing Passwordless Authentication in Laravel 12<\/a> \u2014 reduce password friction entirely.<\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>When building applications for developers, a top UX win is to let users sign in with their existing GitHub account. It reduces friction, avoids extra passwords, and signals trust. In this guide, you\u2019ll add GitHub Login to a Laravel 12 app using Socialite \u2014 with clear steps, fully explained code, and a simple UI button. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":209,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[30,13,20,31],"class_list":["post-205","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-laravel","tag-github","tag-login","tag-social-login","tag-socialite"],"_links":{"self":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/205","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=205"}],"version-history":[{"count":1,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/205\/revisions"}],"predecessor-version":[{"id":208,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/posts\/205\/revisions\/208"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media\/209"}],"wp:attachment":[{"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/media?parent=205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/categories?post=205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/1v0.net\/blog\/wp-json\/wp\/v2\/tags?post=205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}