Web-based authentication integrations in Pugpig apps using PKCE
Table of Contents
Overview
The Pugpig platform offers several methods for users to gain access to paywalled content, these include in-app purchases of subscriptions or editions and a native sign-in UI which we can connect to the authentication system of your choosing.
We also know that in some cases customer will have built mature, powerful sign in flows of their own, and the app would be better served by handing off the authentication process to these systems. To cater for such cases, all Pugpig apps now support presenting an externally-hosted web page to conduct the authentication, and then securely passing the result to the app. You can read more about the secure web views that we use.
If you wish to use the PKCE flow, it will need to work for all users of the app. We are unable to initiate this flow for only a subset of your users. If you have multiple authentication mechanisms (for example an SSO flow for internal users and a separate flow for end users), then you'll need to make sure your login screens handle all of these flows themselves.
Note on flows that leave the app
In some PKCE flows (for example, passwordless logins) an email is sent to the user with a magic link or something like that. We do NOT support magic links re-opening the app at a certain point in the flow. This is because it isn't possible to reliably open an app from a browser redirect. Instead, the email should either:
- provide a code that the user can type into your flow in the app without leaving the app
- set something server side that the flow can poll for, and then continue when done (like Google tend to do)
Some example providers we've used in the past are:
- Auth0
- Piano
- Microsoft B2C
- WP OAuth WordPress Plugin (https://wp-oauth.com/)
- Custom OIDC implementations (which all support OAuth 2.0)
While most major vendors support this, it is possible, although not recommended, to implement the specification yourself. We strongly recommend using a library.
This document explains what information you need to give to us, and what we need to supply to you. Note that the location you get this information, or configure your provider will differ based on your providers. Please refer to their documentation on details for this.
There is also an explainer of how it all works.
Why use PKCE and OAuth?
Here at Pugpig we strongly recommend PKCE over a native login flow. The main reasons for this are:
- PKCE is a part of the OAuth 2.0 specification, which is a widely adopted standard for secure authentication and authorization. Using PKCE ensures that your authentication process is compliant with industry standards and best practices
- Native apps are more susceptible to reverse engineering and other security risks. PKCE mitigates some of these risks by ensuring that even if an attacker reverse engineers your app, they cannot easily impersonate the app and steal the authorization code.
- we support it out of the box - from our side it will only be a few days of configuration.
- It integrates with other flows such as web cross entitlement
- implementing a native SDK will be at least 5 days development per platform (so maybe 10 days), and then the ongoing testing/support/maintenance going forward. This means it'll take it a lot longer for us to roll out, and will cost a whole lot more.
- with the PKCE flow, you get far more control on your side. For example, you could add Social Sign In or Passwordless flows without us changing any code
- with the PKCE flow, you can style the login page however you'd like yourselves (without needing an app release)
- in our experience, the PKCE flow works better with password managers, especially if the user also uses the web site on the same device
- most of the major auth providers support it out of the box
What we need from you
Important: We prefer to transmit sensitive details a secure way. We use Keybase (https://keybase.io/) and our user is pugpig. However, note that by design, using PKCE, nothing below needs to be considered sensitive except maybe the test users. If you even have a client secret, then by definition you aren't using PKCE.
- Client ID - normally one client ID per platform although some providers allow one client ID with multiple callback URLs
- Auth HTML page - the HTML page that displays the login/register screen, and an optional parameter specifying if the default is login or register e.g. https://www.acme.com/login.html?state=register
- Token endpoint - the standard OAuth 2.0 endpoint : https://www.acme.com/oauth/token
- PugPig Test Users - in order to test the flows, you need to provide us a user that covers every case you wish us to test (e.g. inactive, active with some permission, active with different permissions, etc)
- Entitlement specification - The details for the entitlement logic once we have the access token - this can be very custom. Although we do not recommend it, some clients will grant access based on just the access token. See Common Pitfalls.
- Auth subdomain - if you're using our distribution-federated PKCE, an additional subdomain is required to host the login page e.g auth.vanitydomain.com. This is set up in the same way as a vanity domain.
Note that you will still need an Authentication Pack - all the details are here: https://pugpig.zendesk.com/hc/en-us/articles/360003980897-Pugpig-Authentication-Setup-Pack
The only difference is that the sign in call will be handled using OAuth, not via an API call.
Apple, Account Creation & Deletion
If you choose to include a link to create a new account, Apple requires that you provide a way for users to delete their account. This can be a link to a webpage.
Note on Accessibility
The Pugpig Apps are designed to be accessible to as many users as possible. If you are providing a login/registration screen, please ensure it works well with screen readers.
What you need from us
You will need the callback URLs that need to be configured by you in your provider. The callback URLs are usually of the form below. If you do not know your bundle ID/package ID or vanity domain, please let us know:
Platform | Format | Example |
---|---|---|
iOS | bundle.id://authCallback/ | com.acme.app://authCallback |
Android | package.id://authCallback/ | com.acme.app://authCallback |
Web | https://webreader.vanity.root.url/ | https://reader.acme.com/ |
What is OAuth 2.0 with PKCE
Rather than devise a method of our own, our implementation utilises the OAuth 2.0 framework, specifically Proof Key for Code Exchange (PKCE hereafter). This is a security protocol specifically designed for use in mobile apps and is ideally suited to what we're trying to achieve.
Specifically, our apps will allow a user to:
- When required, open a web-page hosted at a destination of your choosing
- Securely verify the validity of the client to the authentication server
- Conduct an authorisation challenge using the user-supplied login details
- Interpret the response and subsequently give access to all, none, or a subset of the app's content.
FLOWS
There are 2 distinct ways a user can enter the PKCE flow.
Immediately after subscribing
- User purchases a subscription via In-App Purchase
- User is presented a prompt asking them to link their subscription
- User agrees to prompt and completes PKCE flow in a webview
- Subscription is now linked to account
Later after subscribing
- User purchases a subscription via In-App Purchase
- User is presented a prompt asking them to link their subscription
- User declines prompt
- User later taps "Link subscription" in the settings/account tab
- User completes PKCE flow in a webview
- Subscription is now linked to account
Receipt postback is always optional, mandating it would result in the app not being accepted to the app stores. If the user does not link their accounts they'd still retain access through their in app purchase, and they would still be able to link their subscription at any time in the future.
Because of our adherence to the PKCE protocol, Pugpig doesn't maintain a spec or implementation guide for our web-based authentication integrations, instead we'll refer you to the docs detailed below to help guide design and planning.
PKCE spec. This is the canonical source of truth that our code's expectations are based on, and should serve to detail the necessary server-side configuration: https://tools.ietf.org/html/rfc7636
OAuth 2.0 PKCE explainer. Details how PKCE fits into the OAuth framework: https://oauth.net/2/pkce
Frequently-encountered issues:
- Identity providers which do not support PKCE or through misconfiguration require a client secret. As all Pugpig apps/clients are considered "public" by RFC7636's definition, they cannot contain secrets, and therefore may only use the PKCE flow.
- Incorrect scope and/or lack of authorising API. The default openid scope is only sufficient for id_token, which is insufficient for authorising access to content. id_tokens are designed for authentication (who you are) not authorisation (what you're allowed to do). We therefore require a scope which yields an access_token which we can use to query your authorisation API.
- In order to save users from having to login every time the use the app/reader, we require a refresh_token, either using the standard "offline_access" scope or some other scope. It is also important that the TTL of the refresh token be at least double the mean duration between user sessions. E.g. If you publish monthly, users are likely to read once a month (unless they go on holiday, in which case they might miss a month etc), but they should not be logged out just for missing one publication. Worse still if the refresh tokens last less time than the mean time between publications, then users are likely to be logged out every time they return to their app/client.
- Not having a separate entitlement call - if you used Claims Based Authorisation (e.g. you do not hit anything endpoint) the granting or revoking of access with take as long as the token TTL, or might even require the user logging in again. This is not recommended!
Importance of persistent cookies
If you are using a third party auth system, they will let you change the domain to be something your own and your users will understand. For example, for Piano see: https://docs.piano.io/faq-article/domain-whitelabeling-for-piano-id/
Additionally persistent cookies means that the auth provider may log users straight in, even if they have logged out of the application, see below. During testing, or if you get stuck with an unexpected account's cookies, please refer to your platform and browser's cache clearing documentation. E.g. iOS Safari https://support.apple.com/en-gb/105082
Log-out considerations
The only thing the clients (mobile app/SPA) have are short-lived access tokens and optionally longer-lived refresh token. This means that our clients log-out by deleting their tokens and letting them expire from lack of use.
While some endpoints exist which allow clients to signal their intent to log out to the OIDC Provider (OP) (e.g. Front-Channel, Back-Channel, RP-Initiated), they are all optional, not widely used, and even if available, they are by definition best-effort and unreliable. For instance think about what happens if a user attempts to log-out while offline.
The best user experience is when OPs remember the user (see above persistent cookie section) but DO NOT auto-sign the user in. E.g. a returning user is shown the option of reusing their existing credentials (stored in the OP's isolated cookie jar) or signing-in as someone else.
Depending on the auth provider, this may be achieved by adding prompt=login
or prompt=select_account
or max_age=0
to the authorisation endpoint as defined by §3.1.2.1 of the OIDC core spec albeit as optional.
Depending on your threat model and security requirements, it is of course acceptable for the OP to store no cookies at all.
Further considerations when concurrency must be limited
Due to the unreliability of log-out endpoints (see above), limited concurrency use-cases are better served by using some combination of:
- short-lived access tokens combined with the above to reduce aggravation caused by frequent requirement to sign back in
- server-side ring-buffer of tokens and revocation of oldest (FIFO) refresh (and access) tokens