Categories
Code iOS Laravel

iOS In-app purchases and Laravel

This is part two of a completely unknown number of linked articles as I’m writing them without even the slightest bit of aforethought. They’re not tutorials as such, but more documenting my progress and thinking as I forge onward building something.

It’s been a while since I’ve used IAP on iOS. This series of articles will follow my erratic thought process as I create the following:

  • A small web app, managing anonymous user accounts and their credit balance
  • An iOS application which allows you to top-up your account, restore purchases etc without the need to create an account.

This post will cover the authentication side of the app – I’m taking the new Laravel Sanctum functionality for a spin.

First thing I did was set up a new Laravel app, install Sanctum, and migrated all the things. I know I’m going to need an endpoint that accomplishes the following:

  • Handle a POST request with some JSON data
  • Check the request comes from a valid source
  • Creates an anonymous user
  • Issues a token for that user
  • Returns a JSON response with the user token

My function ended up looking like this:

public function token(Request $request){

    $validation = Validator::make($request->all(),[
        'key' => 'required',
    ]);

    if($validation->fails())
        return response()->json(["errors"=>$validation->messages()], 200);

    //TODO: Something a bit more robust than this; probably use it to check against an Apps table or something like that
    if($request->key != "supersecretappidentifier")
        abort(403);

    //Create anon user. You could use your own user model. I've stuck with the Eloquent one and faked the data, as I figure I may allow users to create an account at a later stage.
    $faker = Factory::create();
    try {

        $user = User::create([
            'name' => $faker->name,
            'email' => $faker->email,
            'password' => Hash::make($faker->password),
        ]);

        //Create the Sanctum token; again you'd probably use the name of the App or something
        $token = $user->createToken($request->key);

        return response()->json(["token"=>$token->plainTextToken], 200);
    } catch (\Exception $e)
    {
        return response()->json([
            "error"=>true,
            "message" => $e->getMessage()
        ], 400);
    }
}

Hit the endpoint with Postman and you’ll be given a shiny token to use in subsequent requests.

{"token":"C6UHgQXdrtCj7oEipaGCHU0Moq7uBGPpczEu1Lb1ApGpgMHSyfozQSwxvpRm524MdDbtrYBCNqU5s0Z5"}

Great! Now to make an app do it. Swift’s not my strong suit at the moment – I’m still writing most of my iOS code in Objective-C – so please bear with me.

We need functionality that does the following:

  • Checks if there’s a token on the device
  • If not, makes a POST request to our web service
  • Retrieve and store the token in the iCloud Key-value storage

I’m using iCloud storage for this example, but it would be a lot better and safer to use an iCloud based Keychain in a production app.

We save in iCloud so that if the user uses the app on another device – say their iPad – they’ll be linked to the same account at the web service end, whilst remaining anonymous in the eyes of my application. You’ll need to add the iCloud Key-value storage capability to your app to make this work.

The code looks a little like this. I’m using AlamoFire to handle network requests.

let token = NSUbiquitousKeyValueStore.default.string(forKey: "laravelToken")
if(token == nil)
{
    let params: [String:String] = [
        "key": "supersecretappidentifier"
    ]
    AF.request("http://credits.test/api/auth/token",method: .post, parameters: params, encoding:JSONEncoding.default).responseJSON { response in
        switch response.result {
            case .success(let JSON):
                let response = JSON as! NSDictionary
                let token = response.object(forKey: "token")!
                NSUbiquitousKeyValueStore.default.set(token, forKey: "laravelToken")
                NSUbiquitousKeyValueStore.default.synchronize()
                break
            case .failure:
                break
        }
    }
}
else
{
    print(token!)
}

The first time you run the app, it’ll make the request and store the token. Subsequent runs will print out the stored token.

That’s it for today I think – to recap, we’ve created a web service which creates an anonymous user and a token for the app to retrieve and store.

In the next article, I’ll add some credits functionality to the user and display our balance on the iOS device.