When creating a SaaS application, authentication plays a significant role in ensuring users create value that can be seen and associated with a user account.
Most of the time, users will have to provide email and password combinations that tend to be less and less secure over time, as hackers are constantly inventing new ways to breach users’ accounts through social engineering and other methods.
Moreover, integrating an authentication flow inside an app is redundant and requires a lot of effort. That’s why major companies like Apple or Google, with which many people have an account, are creating ways to authenticate users for you. This method is also more secure because both companies have developed methods to ensure your account remains safe, and they have the resources to keep it secure.
In this article, we’ll look at how we can integrate Apple authentication into our web app that’s only based on the client side with JSON Web Token integration passed to the back-end.
SDK Loading
We’ll use the SDK provided by Apple.
To use the SDK, we must first download it. It's a JavaScript library that will live on the page's window object once downloaded through a <script/>
element.
You can either get it from a script element on your HTML's <head/>
, or you can insert the script by creating a <script/>
attribute programmatically.
I prefer the second solution, which is much more flexible, particularly in today's world, where most JavaScript frameworks are component-based.
You can have a component that will be busy dealing with the SDK download, so the SDK will download only when necessary.
- HTML based loading
<script type="text/javascript" src="<https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js>"></script>
- Javascript based loading
await loadScript("<https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js>")
Note that the implementation of the loadScript()
function can be found in the following gist or at the following Pieces link.
The Apple SDK is now loaded. Thanks to the window object, we can use it anywhere in our app.
Implementation
Apple provides very detailed documentation, which is helpful, especially for knowing the data types that methods return.
The documentation is available at this address:
The other pro of Apple SDK is that it provides a very flexible SDK that will allow anyone to design their button as their wish.
Using SDK to Authenticate Users
The good news is that it’s straightforward to set up in terms of code.
Initialization()
The first thing we need to do is to implement the init method:
window.AppleID.auth.init({
clientId : 'myapp.myapp.signin',
scope : 'email name',
redirectURI : window.location.origin,
state : 'SignInUserAuthenticationRequest',
usePopup : true
});
Click to save this command to Pieces
Here, we passed several important fields:
-
clientId
→ TheclientId
is the identifier that will help Apple identify the request to the Apple app you created. To get it from Apple, you must go through several steps we will define in the next section. -
scope
→ Thescope
is essential. You can ask Apple to return the name or/and email of the user that got authenticated. However, it is also important to specify it as Apple will also insert it inside the payload of theJsonWebToken
that we’ll use to auth the user. It’s also handy for the backend, which will create the user in the database with an email associated. -
redirectURI
→ Apple uses theredirectURI
to redirect users once they have finished the authentication process. You must pass the same URL that you will specify in the Apple console. -
state
→ Thestate
is a property for security. It’s a gentle way to identify the request. It allows the client to check that the request you get back from Apple is the one expected. For example, you can put whatever you want in that field, and when you get back the auth request from the user, the field will also be present. You can assert that the field is the same as you initially sent in order to ensure that someone has not tricked you. -
usePopup
→ TheusePopup
feature is explicit. By usingtrue
here, it tells Apple to create a new browser window to authenticate the user. This property seems relatively straightforward, but can lead to inconsistency when used in a WebView environment. We’ll return to this later.
As stated previously, you can find the interface and the method at the following URL:
- ClientConfigI interface
- Init() method
Sign-in()
Once we initialize the SDK, we need to create a button that will react to a click event and start the Apple authentication flow:
button.addEventListener('click', async () => {
await window.AppleID.auth.signIn();
});
Click to save this command to Pieces
Here, the signIn()
method will trigger a popup where users will be able to enter their credentials. During that phase, we no longer control anything. It's Apple's job.
- signIn() method
Listening to Success and Error Events to Authenticate Users
At the end of the flow, Apple needs to give back control with the JsonWebToken
of the authenticated user. To achieve that, the SDK will emit a CustomEvent
in the DOM.
It’s our job to listen to those events and react in consequence.
We need to handle two events: success
and error
.
Success
document.addEventListener("AppleIDSignInOnSuccess", this._onAppleSignInOnSuccess);
_onAppleSignInOnSuccess(event) {
const { state } = event.detail.authorization;
const { email, name } = event.detail.user;
// We are checking that the request we send matches the one we receive.
if (state === "SignInUserAuthenticationRequest") {
const { code, id_token } = event.detail.authorization;
// Do something with id_token and code...
// User details email, name...
} else {
this.onError(new Error('state property is not the one expected'));
}
}
Click to save this command to Pieces
Here, we’re listening to the AppleIDSignInOnSuccess
event that Apple triggers when the user successfully authenticates.
Apple gives back multiples elements:
- SignInResponseI Class
- authorization AuthorizationI interface
-
state
→ Do you remember? It’s the one we passed earlier in the ClientConfigI interface. The condition makes sure that we receive the one we sent earlier. -
code
→ A single-use authorization code. -
id_token
→ This is the most precious piece of information we get back from Apple here. We will use theJsonWebToken
to authenticate the user on the Back-end. - user UserI interface
- name
- Error
document.addEventListener("AppleIDSignInOnFailure", this._onAppleSignInOnFailure);
_onAppleSignInOnFailure(event) {
let { error } = event.detail;
this.onError(new Error(error));
}
Click to save this command to Pieces
We get back an error from Apple when, for some reason, the user did not successfully register. Errors rarely happen because if the user enters the wrong credentials, the error will be on Apple's side and not ours. Note that when the user closes the popup explicitly, Apple triggers an error:
popup_closed_by_user
In your perception, it may not be a proper error, but a potential situation to leverage tracking, for example.
Setting up an App Through the Apple Developer Interface
Now, we’ll look at the essential configuration we have to set up to correctly use the “Sign in with Apple” feature.
You first need an Apple developer account. To have it, you need to pay a subscription to Apple, which is $100 per year.
The setup is available at this page:
https://developer.apple.com/account/
You can follow the video below:
The crucial part is the identifier highlighted at the end of the video:
com.example.signin.test.with.apple.identifier
That identifier is mandatory to have and use as the clientId.
The domains, subdomains, and return URLs are also essential, as your application won’t work if you don’t specify the URL it will run on. Indeed, many misconfigurations can lead to hours of debugging.
Potential Issues Experienced While Developing
This section will cover issues you could encounter, hopefully saving you time.
In the previous section, we configured the app, so we made sure that Apple would know that the following clientId
: com.example.signin.test.with.apple.identifier
has the information needed to work correctly.
Again, that information is critical because a problem with this configuration alone could prevent your app from working.
When we integrated the "Sign-in with Apple" feature, we needed to make sure that it would work inside a WebView. Since the app is misconfigured, though, the Apple SDK was returning an error from the error event with the following payload:
popup_closed_by_browser
The error was not too explicit, so we thought it was related to the property we specified: usePopup.
So, we tried to set it to false, and it worked since the error was gone. However, the misconfiguration triggered another error on the Apple side. The redirect URL parameter was misconfigured at that time, so we went to the developer tools interface and configured the correct redirects. Then, we thought that everything would work, but it wasn’t the case. Apple was reloading the page because of the misuse of the property usePopup. So, we went back to this property to set it to true – that time with the correct configuration – and finally, it worked.
To debug the WebView inside the app, we used Safari and the IOS simulator inside XCode.
Here are the steps for debugging:
- Open Safari → Click on Safari on the top left bar → Preferences → Tick the checkbox “Show Develop menu in the menu bar.”
- Then, head to XCode, and create a new IOS project from Storyboard. Insert the following code into the ViewController file:
- https://gist.github.com/PaulRosset/cea982e09d09e609679b7e848e397674 or the following Pieces link: Click to save this command to Pieces
- Once you’ve built and started the project inside the simulator, you can return to Safari. At the top of the screen in the top bar, click “Develop.” You should see the simulator devices appearing. Click on your simulator, and finally, click on the WebView to spawn the Safari developer tool.
This tip for debugging is only for IOS WebView usage. However, having an environment where the developer is comfortable debugging is essential.
Conclusion
Integrating a social provider into a front-end app is a pleasant experience for a developer. The spread of social provider integrations will likely be appreciated by developers.
A social provider simplifies the authentication step, which can be redundant for users. Hence, easing it will contribute to a positive user experience by eliminating the need to complete the email/password + email confirmation step. This also eliminates the need to remember passwords and removes the associated challenges, thus improving the signup and sign-in user experience.
Top comments (0)