Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flutter_appauth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [12.1.0]

* [iOS] Added support for `https` redirect URIs on iOS 17.4 and newer. When the redirect URL uses the `https` scheme, the plugin now starts the `ASWebAuthenticationSession` using the `callbackWithHTTPSHost:path:` API. This applies to both the default and ephemeral `ASWebAuthenticationSession` external user agents. No Dart API changes are required — pass an `https` redirect URL as usual. Note that the app must declare the matching Associated Domains entitlement (e.g. `webcredentials:<host>`) for the callback to be delivered.

## [12.0.2]

* [iOS][macOS] improved SPM (Swift Package Manager) compatibility. Thanks to PR from [JarvanMo](https://github.com/JarvanMo)
Expand Down
47 changes: 47 additions & 0 deletions flutter_appauth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Ephemeral Sessions (iOS and macOS only)](#ephemeral-sessions-ios-and-macos-only)
- [Android setup](#android-setup)
- [iOS/macOS setup](#iosmacos-setup)
- [Using https redirect URIs on iOS 17.4+](#using-https-redirect-uris-on-ios-174)
- [API docs](#api-docs)
- [FAQs](#faqs)

Expand Down Expand Up @@ -260,6 +261,52 @@ Go to the `Info.plist` for your iOS/macOS app to specify the custom scheme so th

Note: iOS apps generate a file called `cache.db` which contains the table `cfurl_cache_receiver_data`. This table will contain the access token obtained after the login is completed. If the potential data leak represents a threat for your application then you can disable the information caching for the entire iOS app (ex. https://kunalgupta1508.medium.com/data-leakage-with-cache-db-2d311582cf23).

### Using https redirect URIs on iOS 17.4+

Starting with iOS 17.4, you can use an `https` redirect URI (a universal-link-style URL such as `https://app.example.com/callback`) instead of a custom scheme. When the redirect URL you pass uses the `https` scheme, the plugin automatically starts the `ASWebAuthenticationSession` using Apple's `callbackWithHTTPSHost:path:` API. No Dart API change is required — you just pass an `https` redirect URL.

This only applies to iOS 17.4 and newer. The plugin's minimum deployment target is unchanged, and the feature is gated behind a runtime `@available(iOS 17.4, *)` check so older devices are unaffected. However, the API genuinely does not exist before 17.4, so on earlier versions an `https` redirect cannot work. If you need to support devices below 17.4, branch on the OS version and fall back to a custom scheme:

```dart
import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart';

Future<String> redirectUrl() async {
if (Platform.isIOS) {
final info = await DeviceInfoPlugin().iosInfo;
final parts = info.systemVersion.split('.');
final major = int.tryParse(parts.elementAt(0)) ?? 0;
final minor = parts.length > 1 ? int.tryParse(parts[1]) ?? 0 : 0;
final supportsHttps = major > 17 || (major == 17 && minor >= 4);
if (supportsHttps) {
return 'https://app.example.com/oauth/callback';
}
}
return 'com.example.app://oauth/callback';
}
```

If your app's minimum iOS version is already 17.4 or higher, you can drop the branching and always use the `https` redirect URL.

Requirements when using an `https` redirect URI:

1. **Associated Domains entitlement.** Add a `webcredentials` entry (not `applinks`) for the callback host. `ASWebAuthenticationSession`'s `https` callback uses the `webcredentials` association to verify your app owns the domain — it does not go through the universal-link (`applinks`) machinery, and iOS refuses to start the session without it (validation is lenient on the Simulator, so always test on a real device):

```xml
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:app.example.com</string>
</array>
```

2. **`apple-app-site-association` file** hosted at `https://app.example.com/.well-known/apple-app-site-association` listing your app under the `webcredentials` key:

```json
{ "webcredentials": { "apps": ["TEAMID.com.example.myapp"] } }
```

3. **Register both redirect URIs with your identity provider** if you support older devices, since OAuth servers match `redirect_uri` exactly. Keep the custom scheme registered in `Info.plist` (see above) for the fallback path.


## API docs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ @implementation AppAuthIOSAuthorization
if (exchangeCode) {
id<OIDExternalUserAgent> agent =
[self userAgentWithViewController:rootViewController
externalUserAgent:externalUserAgent];
externalUserAgent:externalUserAgent
redirectURL:[NSURL URLWithString:redirectUrl]];
return [OIDAuthState
authStateByPresentingAuthorizationRequest:request
externalUserAgent:agent
Expand Down Expand Up @@ -68,7 +69,8 @@ @implementation AppAuthIOSAuthorization
} else {
id<OIDExternalUserAgent> agent =
[self userAgentWithViewController:rootViewController
externalUserAgent:externalUserAgent];
externalUserAgent:externalUserAgent
redirectURL:[NSURL URLWithString:redirectUrl]];
return [OIDAuthorizationService
presentAuthorizationRequest:request
externalUserAgent:agent
Expand Down Expand Up @@ -136,7 +138,8 @@ @implementation AppAuthIOSAuthorization
UIViewController *rootViewController = [self rootViewController];
id<OIDExternalUserAgent> externalUserAgent =
[self userAgentWithViewController:rootViewController
externalUserAgent:requestParameters.externalUserAgent];
externalUserAgent:requestParameters.externalUserAgent
redirectURL:postLogoutRedirectURL];

return [OIDAuthorizationService
presentEndSessionRequest:endSessionRequest
Expand Down Expand Up @@ -164,17 +167,22 @@ @implementation AppAuthIOSAuthorization

- (id<OIDExternalUserAgent>)
userAgentWithViewController:(UIViewController *)rootViewController
externalUserAgent:(NSNumber *)externalUserAgent {
if ([externalUserAgent integerValue] == EphemeralASWebAuthenticationSession) {
return [[OIDExternalUserAgentIOSNoSSO alloc]
initWithPresentingViewController:rootViewController];
}
externalUserAgent:(NSNumber *)externalUserAgent
redirectURL:(NSURL *)redirectURL {
if ([externalUserAgent integerValue] == SafariViewController) {
return [[OIDExternalUserAgentIOSSafariViewController alloc]
initWithPresentingViewController:rootViewController];
}
return [[OIDExternalUserAgentIOS alloc]
initWithPresentingViewController:rootViewController];
// Both the default (SSO) and ephemeral ASWebAuthenticationSession modes are
// served by OIDExternalUserAgentIOSNoSSO so that `https` redirect URIs are
// supported on iOS 17.4+. AppAuth's own OIDExternalUserAgentIOS only supports
// custom-scheme callbacks.
BOOL prefersEphemeralSession =
[externalUserAgent integerValue] == EphemeralASWebAuthenticationSession;
return [[OIDExternalUserAgentIOSNoSSO alloc]
initWithPresentingViewController:rootViewController
prefersEphemeralSession:prefersEphemeralSession
redirectURL:redirectURL];
}

- (UIViewController *)rootViewController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
@brief OIDExternalUserAgentIOSNoSSO is a custom user agent based on the
default user agent in the AppAuth iOS SDK found here:
https://github.com/openid/AppAuth-iOS/blob/master/Source/iOS/OIDExternalUserAgentIOS.h
Ths user agent allows setting `prefersEphemeralSession` flag on iOS
13 or newer to avoid cookies being shared across the device.
This user agent allows setting the `prefersEphemeralSession` flag on
iOS 13 or newer to avoid cookies being shared across the device. It also
supports `https` redirect URIs on iOS 17.4 or newer by
using `ASWebAuthenticationSession`'s `callbackWithHTTPSHost:path:` API when
the redirect URL passed to the designated initializer uses the `https`
scheme.
@copydetails
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,13 +40,32 @@ API_UNAVAILABLE(macCatalyst)
"This method will not work on iOS 13, use "
"initWithPresentingViewController:presentingViewController");

/*! @brief The designated initializer.
/*! @brief Convenience initializer that prefers an ephemeral session and does
not enable `https` redirect URI handling.
@param presentingViewController The view controller from which to present
the
\SFSafariViewController.
the \SFSafariViewController.
*/
- (nullable instancetype)initWithPresentingViewController:
(UIViewController *)presentingViewController NS_DESIGNATED_INITIALIZER;
(UIViewController *)presentingViewController;

/*! @brief The designated initializer.
@param presentingViewController The view controller from which to present
the \SFSafariViewController.
@param prefersEphemeralSession Whether the underlying
`ASWebAuthenticationSession` should use a private (ephemeral) browser session
so cookies are not shared across the device.
@param redirectURL The redirect URL of the request. When this uses the
`https` scheme, the `ASWebAuthenticationSession` is started with a
`callbackWithHTTPSHost:path:` callback on iOS 17.4 or newer so that HTTPS
URLs can be used as redirect URIs. May be nil, in which case the legacy
`callbackURLScheme:` behaviour is used.
*/
- (nullable instancetype)
initWithPresentingViewController:
(UIViewController *)presentingViewController
prefersEphemeralSession:(BOOL)prefersEphemeralSession
redirectURL:(nullable NSURL *)redirectURL
NS_DESIGNATED_INITIALIZER;

@end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ @interface OIDExternalUserAgentIOSNoSSO () <SFSafariViewControllerDelegate>

@implementation OIDExternalUserAgentIOSNoSSO {
UIViewController *_presentingViewController;
BOOL _prefersEphemeralSession;
NSURL *_redirectURL;

BOOL _externalUserAgentFlowInProgress;
__weak id<OIDExternalUserAgentSession> _session;
Expand All @@ -47,6 +49,16 @@ - (nullable instancetype)init {

- (nullable instancetype)initWithPresentingViewController:
(UIViewController *)presentingViewController {
return [self initWithPresentingViewController:presentingViewController
prefersEphemeralSession:YES
redirectURL:nil];
}

- (nullable instancetype)
initWithPresentingViewController:
(UIViewController *)presentingViewController
prefersEphemeralSession:(BOOL)prefersEphemeralSession
redirectURL:(nullable NSURL *)redirectURL {
self = [super init];
if (self) {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
Expand All @@ -55,6 +67,8 @@ - (nullable instancetype)initWithPresentingViewController:
#endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000

_presentingViewController = presentingViewController;
_prefersEphemeralSession = prefersEphemeralSession;
_redirectURL = redirectURL;
}
return self;
}
Expand All @@ -78,38 +92,73 @@ - (BOOL)presentExternalUserAgentRequest:(id<OIDExternalUserAgentRequest>)request
// (rdar://40809553)
if (!UIAccessibilityIsGuidedAccessEnabled()) {
__weak OIDExternalUserAgentIOSNoSSO *weakSelf = self;
NSString *redirectScheme = request.redirectScheme;
ASWebAuthenticationSession *authenticationVC =
[[ASWebAuthenticationSession alloc]
void (^completionHandler)(NSURL *_Nullable, NSError *_Nullable) = ^(
NSURL *_Nullable callbackURL, NSError *_Nullable error) {
__strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_webAuthenticationVC = nil;
if (callbackURL) {
[strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL];
} else {
NSError *safariError = [OIDErrorUtilities
errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow
underlyingError:error
description:nil];
[strongSelf->_session failExternalUserAgentFlowWithError:safariError];
}
};

ASWebAuthenticationSession *authenticationVC = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 170400
// On iOS 17.4 or newer, an `https` redirect URI requires the
// `callbackWithHTTPSHost:path:` API; `callbackURLScheme:` only supports
// custom schemes.
if (@available(iOS 17.4, *)) {
// Only use the https callback for a well-formed https redirect URL with
// a host. Anything else (a custom scheme, or a nil/malformed redirect
// URL such as an end-session request without a postLogoutRedirectUrl)
// must fall through to the callbackURLScheme: path below. Guarding on
// `scheme.length` is essential: `_redirectURL.scheme` is nil when
// `_redirectURL` is nil, and `[nil caseInsensitiveCompare:@"https"]`
// returns NSOrderedSame, which would otherwise pass a nil host/path to
// the nonnull callbackWithHTTPSHost:path: and raise an exception.
if (_redirectURL.scheme.length &&
[_redirectURL.scheme caseInsensitiveCompare:@"https"] ==
NSOrderedSame &&
_redirectURL.host.length) {
// callbackWithHTTPSHost:path: requires a nonnull path; treat a
// host-only redirect URL as the root path.
NSString *redirectPath =
_redirectURL.path.length ? _redirectURL.path : @"/";
ASWebAuthenticationSessionCallback *callback =
[ASWebAuthenticationSessionCallback
callbackWithHTTPSHost:_redirectURL.host
path:redirectPath];
authenticationVC = [[ASWebAuthenticationSession alloc]
initWithURL:requestURL
callbackURLScheme:redirectScheme
completionHandler:^(NSURL *_Nullable callbackURL,
NSError *_Nullable error) {
__strong OIDExternalUserAgentIOSNoSSO *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_webAuthenticationVC = nil;
if (callbackURL) {
[strongSelf->_session
resumeExternalUserAgentFlowWithURL:callbackURL];
} else {
NSError *safariError = [OIDErrorUtilities
errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow
underlyingError:error
description:nil];
[strongSelf->_session
failExternalUserAgentFlowWithError:safariError];
}
}];
callback:callback
completionHandler:completionHandler];
}
}
#endif
if (!authenticationVC) {
NSString *redirectScheme = request.redirectScheme;
authenticationVC =
[[ASWebAuthenticationSession alloc] initWithURL:requestURL
callbackURLScheme:redirectScheme
completionHandler:completionHandler];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13.0, *)) {
authenticationVC.presentationContextProvider = self;
}
#endif
_webAuthenticationVC = authenticationVC;
if (@available(iOS 13.0, *)) {
authenticationVC.prefersEphemeralWebBrowserSession = YES;
authenticationVC.prefersEphemeralWebBrowserSession =
_prefersEphemeralSession;
}
openedUserAgent = [authenticationVC start];
}
Expand Down
2 changes: 1 addition & 1 deletion flutter_appauth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: flutter_appauth
description: This plugin provides an abstraction around the Android and iOS
AppAuth SDKs so it can be used to communicate with OAuth 2.0 and OpenID
Connect providers
version: 12.0.2
version: 12.1.0
homepage: https://github.com/MaikuB/flutter_appauth/tree/master/flutter_appauth

environment:
Expand Down