Improving sharing authenticated links on iOS

When you share a link using the system share sheet, iOS will automatically fetch the underlying page to get the metadata to render a rich preview for the link. This a problem though for links that require authentication. When iOS tries to fetch it, it will be redirected to a sign in page, and iOS will instead display the metadata for the sign in screen instead of the original link.

This makes sense for publicly shared links: you don’t want to leak private data, and iOS wouldn’t have access to it anyway. But in many cases, it provides a bad experience for all parties. The sharer might think they made a mistake or the wrong thing will be shared. The recipient will see a link with no context until they click through.

Screenshot of share sheet showing a URL that says 'Sign In'
Authenticated links show Sign In page info

The Fix

Typically though, if we’re sharing something, we already have the context for it. What we need is a way to provide it ourselves instead of iOS trying to fetch it. We can do exactly that by providing our own LPLinkMetadata to the share sheet, which is the type that the share sheet populates under the hood with the metadata from the fetched URL.

We do this by creating a type that conforms to UIActivityItemSource and implementing the func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? method (docs). I created a small reusable type for this called URLShareItem to use throughout the app:

final class URLShareItem: NSObject, UIActivityItemSource {
    let url: URL
    let title: String
    let icon: UIImage

    init(url: URL, title: String, icon: UIImage) {
        self.url = url
        self.title = title
        self.icon = icon
        super.init()
    }

    func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
        url
    }

    func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
        url
    }

    func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? {
        let metadata = LPLinkMetadata()
        metadata.title = title
        metadata.url = url
        metadata.originalURL = url
        metadata.iconProvider = NSItemProvider(object: icon)

        return metadata
    }
}

You can present a sheet with this item instead of a URL to provide the extra context:

func share() {
    // Provide the url and a relevant title/icon
    let item = URLShareItem(url: url, title: title, icon: appIcon)
    let activitySheet = UIActivityViewController(activityItems: [item], applicationActivities: nil)
    present(activitySheet, animated: true)
}

Now we can provide the actual title of the document instead of a sign in page and even customize the icon to be more accurate.

Screenshot of share sheet with custom title
Customized title and icon

Unfortunately though, it’s not all roses. First, since we’re bypassing the network request, you have to provide your own icon instead of getting the favicon for free. Here we provide a relevant icon for the content we’re sharing, or you might use your own app icon if sharing a link to your app since that is most likely the same or similar as the website favicon image.

Second issue is that not all apps use the provided metadata, some will still just use the underlying URL and fetch it themselves, undoing all our hard work. Messages gets it right, and I’m hoping more apps will improve that, but I think this is still a worthwhile improvement since the user gets a better experience while sharing and confident that the URL they’re sharing has the correct context.

UX Improvement Bonus

Even if your links don’t require authentication and the fetching will work correctly, this can still be a nice UX improvement to implement. When you present a share sheet with a URL, iOS will immediately display the raw url address and placeholder icon for the URL while it fetches the metadata in the background. Once the URL has been fetched, it updates UI with the correct information, which doesn’t look great. If you provide the LPLinkMetadata as above, this request is skipped and the share sheet is presented immediately with the final data and no UI updates occur.

Animation showing two-stage loading of share sheet
Share sheet with network fetch
Animation showing immediate loading of share sheet
Share sheet with provided metadata