In this short tutorial, we'll learn how to implement Apple sign in within Laravel backed API.

Follow along the steps & in the end you’ll have successfully implemented Apple sign-in functionality in your Laravel app.

Scenario

Recently, i came across an objection by apple while submitting my app to the Apple Store that the app should have Apple sign-in functionality for iOS 13 and on. My backend is built on Laravel so I’ll have to make it as soon as i can. So let’s start setting up the Apple sign-in feature.

Please note that, I’ve used Laravel Passport for authentication in my Laravel API.

Prerequisites

For successfully implementing the Apple signin with Laravel, you’ll need to have the following to get started.

Step # 01 – Install the Dependencies

Inside of your project root, run the following command:

composer require socialiteproviders/apple

Step # 02 – Setup the Service Provider & Event Listeners

In config/app.php, register the following ServiceProvider

\SocialiteProviders\Manager\ServiceProvider::class,

Next is we register the Event Listeners required to work with this library.

In app\Providers\EventServiceProvider.php, register the following Event Listener:

\SocialiteProviders\Manager\SocialiteWasCalled::class => [
            'SocialiteProviders\\Apple\\AppleExtendSocialite@handle',
        ],

Step # 03 – Setting the constants

In config/services.php, setup the Apple credentials:

"apple" => [
        "client_id" => "<your_client_id>",
        "client_secret" => "<your_client_secret>",
    ],

Replace client_id and client_secret with your values.

Step # 04 – Route Setup

For this setup, we’ll create an api route.

In routes/api.php,

Route::post('apple/login','API\\Auth\\AppleLoginController@login');

Step # 05 – Setup Controller & Methods

Let’s create an AppleLoginController inside of API\Auth directory. Lets use php artisan to do so

php artisan make:controller API\\Auth\\AppleLoginController

Now inside of the controller, add the login method

namespace App\Http\Controllers\API\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User as OAuthTwoUser;
use Symfony\Component\HttpFoundation\Response;

class AppleLoginController extends Controller
{

    /**
     * @param  Request  $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function appleLogin(Request $request)
    {
        $provider = 'apple';
        $token = $request->token;

        $socialUser = Socialite::driver($provider)->userFromToken($token);
        $user = $this->getLocalUser($socialUser);

        $client = DB::table('oauth_clients')
            ->where('password_client', true)
            ->first();
        if (!$client) {
            return response()->json([
                'message' => trans('validation.passport.client_error'),
                'status' => Response::HTTP_INTERNAL_SERVER_ERROR
            ], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        $data = [
            'grant_type' => 'social',
            'client_id' => $client->id,
            'client_secret' => $client->secret,
            'provider' => 'apple',
            'access_token' => $token
        ];
        $request = Request::create('/oauth/token', 'POST', $data);

        $content = json_decode(app()->handle($request)->getContent());
        if (isset($content->error) && $content->error === 'invalid_request') {
            return response()->json(['error' => true, 'message' => $content->message]);
        }

        return response()->json(
            [
                'error' => false,
                'data' => [
                    'user' => $user,
                    'meta' => [
                        'token' => $content->access_token,
                        'expired_at' => $content->expires_in,
                        'refresh_token' => $content->refresh_token,
                        'type' => 'Bearer'
                    ],
                ]
            ],
            Response::HTTP_OK
        );
    }

    /**
     * @param  OAuthTwoUser  $socialUser
     * @return User|null
     */
    protected function getLocalUser(OAuthTwoUser $socialUser): ?User
    {
        $user = User::where('email', $socialUser->email)->first();

        if (!$user) {
            $user = $this->registerAppleUser($socialUser);
        }

        return $user;
    }


    /**
     * @param  OAuthTwoUser  $socialUser
     * @return User|null
     */
    protected function registerAppleUser(OAuthTwoUser $socialUser): ?User
    {
       $user = User::create(
            [
                'full_name' => request()->fullName ? request()->fullName : 'Apple User',
                'email' => $socialUser->email,
                'password' => Str::random(30), // Social users are password-less
                
            ]
        );
        return $user;
    }

}

Step #06 – Test it out

Apple sometimes doesn’t provide the name, depending on the user permissions. Here we’re getting token and fullName in from the request.

Open up postman, hit the endpoint with these params & if it doesn’t gives you any errors, try out hitting the API from the app and it should work just fine.

Please note that apple token can be obtained by apple SDK and its validity lasts 5 minutes.

Bonus Step – Generating Client Secret

Create a file client_secret.rb in your project folder & place your key.txt file in the same directory as well.

require 'jwt'

key_file = 'key.txt'
team_id = '<your_team_id>'
client_id = '<your_client_id>'
key_id = '<your_key_id>'

ecdsa_key = OpenSSL::PKey::EC.new IO.read key_file

headers = {
'kid' => key_id
}

claims = {
    'iss' => team_id,
    'iat' => Time.now.to_i,
    'exp' => Time.now.to_i + 86400*180,
    'aud' => 'https://appleid.apple.com',
    'sub' => client_id,
}

token = JWT.encode claims, ecdsa_key, 'ES256', headers

puts token

This file require jwt to run, so make sure you’ve jwt gem installed

sudo gem install jwt

Replace the values with your keys, upon running this will provide us with the client_secret.

Now let’s run this file by:

ruby client_secret.rb

It’ll output the client_secret, copy and paste in the config/services.php file.

References

You may also Like