Clifton Labrum
Clifton Labrum

Reputation: 14158

CloudKit JS Hello World Example: No Auth Method Found

I'm working through Apple's CloudKit Catalog example, and I'm just trying to get authentication to work. I want to run this JS code in a browser window (not Node JS). I have taken their code from their site like this:

<!DOCTYPE html>
<html lang="en">
  <head></head>
  <body>
  <script>
  window.addEventListener('cloudkitloaded', function() {

  CloudKit.configure({
    containers: [{
      containerIdentifier: 'iCloud.com.example.CloudKitCatalog',
      apiToken: 'b86f0b5db29f04f45badba0366f39b7130a505f07765b5ba3a2ceb0cb3d96c0c',
      persist: true,
      environment: 'production',
      signInButton: { id: 'sign-in-button', theme: 'black' },
      signOutButton: { id: 'sign-out-button', theme: 'black' }
    }]
  })

  var container = CloudKit.getDefaultContainer()

  //-----
  function gotoAuthenticatedState(userIdentity) {
    var name = userIdentity.nameComponents
    if(name) {
      displayUserName(name.givenName + ' ' + name.familyName)
    } else {
      displayUserName('User record name: ' + userIdentity.userRecordName)
    }
    container
      .whenUserSignsOut()
      .then(gotoUnauthenticatedState)
  }

  //-----
  function gotoUnauthenticatedState(error) {
    if(error && error.ckErrorCode === 'AUTH_PERSIST_ERROR') {
      showDialogForPersistError()
    }
    displayUserName('Unauthenticated User')
    container
      .whenUserSignsIn()
      .then(gotoAuthenticatedState)
      .catch(gotoUnauthenticatedState)
  }

  // Check a user is signed in and render the appropriate button.
  return container.setUpAuth()
    .then(function(userIdentity) {
      // Either a sign-in or a sign-out button was added to the DOM.
      // userIdentity is the signed-in user or null.
      if(userIdentity) {
        gotoAuthenticatedState(userIdentity)
      } else {
        gotoUnauthenticatedState()
      }
    })

  })

  </script>

  <script src="https://cdn.apple-cloudkit.com/ck/2/cloudkit.js" async></script>

  <div id="sign-in-button"></div>
  <div id="sign-out-button"></div>
</body>
</html>

But I keep getting these two errors:

cloudkit.js:14 GET https://api.apple-cloudkit.com/database/1/iCloud.com.example.CloudKitCatalog/production/public/users/caller?ckjsBuildVersion=2005ProjectDev34&ckjsVersion=2.6.1&clientId=735f8b19-3218-4493-80e4-7ab0b39041ac 401 (Unauthorized)
(anonymous) @ cloudkit.js:14

And immediately thereafter...

cloudkit.js:14 Uncaught (in promise) t {
  _ckErrorCode: "AUTHENTICATION_FAILED", 
  _uuid: "3b5cf33d-d56d-414f-83a4-6f320cd915b2", 
  _reason: "no auth method found", 
  _serverErrorCode: "AUTHENTICATION_FAILED", 
  _extensionErrorCode: undefined, …
}

I feel like this should be the easy part, but I can't even get the first setUpAuth() function to work. I've also tried my own CloudKit containerIdentifier and apiToken but I get the same error.

Does anyone know what I'm doing wrong?

Upvotes: 4

Views: 950

Answers (2)

Greg Brown
Greg Brown

Reputation: 53

There seems to be some issue in cloudkit.js (when not using Node?) when it tries to save the auth cookie. You can fake your way around that with something like this:

function MyAuth()
{
}

MyAuth.prototype.putToken = function (containerIdentifier, authToken)
{
  console.log('save cookie: ' + containerIdentifier + ' = ' + authToken);
  sessionStorage.setItem('auth.' + containerIdentifier, JSON.stringify(authToken));
}

MyAuth.prototype.getToken = function(containerIdentifier)
{
  console.log('load cookie: ' + containerIdentifier);
  return JSON.parse(sessionStorage.getItem('auth.' + containerIdentifier));
}

var gAuth = new MyAuth();

and setting up your container with:

CloudKit.configure({
  containers: [<container config>],
  services: {authTokenStore: gAuth}
});

sessionStorage will hold the cookie until the browser is closed. The json conversion is used because when you sign out it sets the value to null which gets saved as "null" and then loaded incorrectly. You could also store it locally in a hash, or in an actual cookie.

Also your configuration is incorrect because you've flattened out and removed the apiTokenAuth member:

CloudKit.configure({
  containers: [{
    containerIdentifier: 'iCloud.com.example.CloudKitCatalog',
    environment: 'production',
    apiTokenAuth: {
      apiToken: 'b86f0b5db29f04f45badba0366f39b7130a505f07765b5ba3a2ceb0cb3d96c0c',
      persist: true,
      signInButton: { id: 'sign-in-button', theme: 'black' },
      signOutButton: { id: 'sign-out-button', theme: 'black' }
    }
  }]
})

This works with either v1 or v2 of cloudkit.js.

Upvotes: 0

Clifton Labrum
Clifton Labrum

Reputation: 14158

It seems there are only like 5 people in the world who understand the CloudKit JS API, and all of them have been reassigned to work on ARKit and they ignore Stack Overflow. ;)

For the sake of posterity, I learned that I had two things wrong.

== 1 ==

You have to ignore the deprecation warning and use the version 1 JS file (ck/1/cloudkit.js):

https://cdn.apple-cloudkit.com/ck/1/cloudkit.js

== 2 ==

Despite what the documentation says, you actually can't specify the authentication button <div id=""> in your container configuration. The Sign In button started showing up for me when I changed my HTML to use the default Apple <div> IDs:

<div id="apple-sign-in-button"></div>
<div id="apple-sign-out-button"></div>

I hope that helps someone else. :)

Upvotes: 5

Related Questions