This plugin offers a WebView implementation approach for OAuth authorization/authentication with identity providers. In the case of Web it doesn't use WebView, instead it loads the page directly in the browser.
Platform | Compatibility |
---|---|
Android | ✅ |
iOS | ✅ |
Web | ✅ |
Other plugins like Flutter AppAuth uses native implementation of AppAuth which in turn uses SFAuthenticationSession
and CustomTabs
for iOS and Android respectively. When using SFAuthenticationSession
and CustomTabs
your app will/could present some problems like:
- UI: users will notice a breaking UI difference when system browser opens to handle the identity provider authentication process.
- In iOS an annoying system dialog shows up every time the user tries to authenticate, indicating that the current app and browser could share their information.
- Your system browser cache is shared with your app which is good and bad, bad because any cache problem due to your every day navigation use could affect your app authentication and the only way to clean cache it's by cleaning system browser cache at operating system level.
- Authentication page will use the locale from System Browser which in fact uses the Operating System locale, this means if your app uses different language than the Operating System then authentication page will show different internationalization than your app.
With this plugin you will get:
- Full control over the UI, WebView will run inside your app so Theme and Color Scheme will be yours to choose, in fact you can add AppBar or FloatingActionButton or whatever you thinks it's necessary to your UI.
- No system dialog will be shown when users tries to authenticate.
- Users will not be affected by any system browser problem cache and also will be able to clean app browser cache from the authentication screen itself.
- Authentication page locale can be set from app using the
contentLocale
property to ensure the same locale. By default Operating System locale will be used if nocontentLocale
is specified. - Custom headers can be set if necessary.
Notes:
contentLocale
will apply only if the authentication page supports the specifiedLocale('...')
and accepts the header:'Accept-Language': 'es-ES'
.- Web implementation deson't allow
contentLocale
, custom headers, nor full control over UI, because web implementation loads the page directly in the browser.
- Static constants key for tooltips, message and hero tags were moved from
OAuthWebView
toBaseWebView
OAuthWebView
renamedonSuccess
function toonSuccessAuth
.
Probably you will not need to do any migration. You only need to do changes:
- If you override some of the plugin widgets in your code, you will have to check for the differences and apply it to your code.
- If you plan to support web you will have to initialize the plugin, this is explained below in the Initialization section.
- Static constants key for tooltips, message and hero tags were moved from
BaseWebView
toBaseConfiguration
. OAuthWebScreen
andOAuthWebView
now requires aOAuthConfiguration
object andBaseWebScreen
andBaseWebView
now requires aBaseConfiguration
object instead of properties directly.- Make sure your pubspec
enironment
fits the following constraints:sdk: ">=2.19.0 <3.0.0"
andflutter: ">=2.0.0"
.
As stated before this plugin uses WebView implementation specifically the plugin flutter_inappwebview. For any WebView related problem please check the documentation of that plugin at docs.
Just add the internet permission to your AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.example">
<uses-permission android:name="android.permission.INTERNET"/>
<application>
...
</application>
Just add this to your Info.plist
<plist version="1.0">
<dict>
....
....
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
....
....
</dict>
</plist>
No setup required
- This plugin offers a widget
OAuthWebView
which handles all the authorization/authentication and navigation logic; this widget can be used in any widget tree of your current app or as an individual authentication screen. For individual authentication screen it offers the widgetOAuthWebScreen
which can be started as a new route and also handles the Android back button to navigate backward when applies. - In addition, this plugin offers a simple widget
BaseWebView
which may be useful for cases in which you need to handle a link to your Auth server, let's say for email confirmation, or password reset, etc. This widget will handle the web UI and automatically get back to you when loaded any of the specifiedredirectUrls
. TheBaseWebView
widget works very similar toOAuthWebView
, it can be used in any widget tree of your current app or as an individual screen. For individual screen it offers the widgetBaseWebScreen
which can be started as a new route and also handles the Android back button to navigate backward when applies.
In the main()
function you should initialize this plugin just before runApp(...):
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await OAuthWebAuth.instance.init();
runApp(const MyApp());
}
- Note that it's required to call
WidgetsFlutterBinding.ensureInitialized();
before.init()
. In the case of testingTestWidgetsFlutterBinding.ensureInitialized()
should be called. - The plugin initialization is only required for Web, if you plan to use this plugin only for iOS or Android you can ignore initialization.
- User successfully authenticates, which returns an OAuth2 Credentials object with the access-token and refresh-token.
- An error occurred during authorization/authentication, or maybe certificate validation failed.
- User canceled authentication.
This plugin offers two variants to handle these outputs.
Awaiting response from navigator route Future
.
void loginV1() async {
final result = await OAuthWebScreen.start(
context: context,
configuration: OAuthConfiguration(
authorizationEndpointUrl: authorizationEndpointUrl,
tokenEndpointUrl: tokenEndpointUrl,
clientSecret: clientSecret,
clientId: clientId,
redirectUrl: redirectUrl,
scopes: scopes,
promptValues: const ['login'],
loginHint: 'johndoe@mail.com',
onCertificateValidate: (certificate) {
///This is recommended
/// Do certificate validations here
/// If false is returned then a CertificateException() will be thrown
return true;
},
textLocales: {
///Optionally texts can be localized
BaseConfiguration.backButtonTooltipKey: 'Ir atrás',
BaseConfiguration.forwardButtonTooltipKey: 'Ir adelante',
BaseConfiguration.reloadButtonTooltipKey: 'Recargar',
BaseConfiguration.clearCacheButtonTooltipKey: 'Limpiar caché',
BaseConfiguration.closeButtonTooltipKey: 'Cerrar',
BaseConfiguration.clearCacheWarningMessageKey:
'¿Está seguro que desea limpiar la caché?',
},
contentLocale: Locale('es'),
refreshBtnVisible: false,
clearCacheBtnVisible: false,
));
if (result != null) {
if (result is Credentials) {
authResponse = getPrettyCredentialsJson(result);
} else {
authResponse = result.toString();
}
} else {
authResponse = 'User cancelled authentication';
}
setState(() {});
}
Using callbacks
void loginV2() {
OAuthWebScreen.start(
context: context,
configuration: OAuthConfiguration(
authorizationEndpointUrl: authorizationEndpointUrl,
tokenEndpointUrl: tokenEndpointUrl,
clientSecret: clientSecret,
clientId: clientId,
redirectUrl: redirectUrl,
scopes: scopes,
promptValues: const ['login'],
loginHint: 'johndoe@mail.com',
onCertificateValidate: (certificate) {
///This is recommended
/// Do certificate validations here
/// If false is returned then a CertificateException() will be thrown
return true;
},
textLocales: {
///Optionally text can be localized
BaseConfiguration.backButtonTooltipKey: 'Ir atrás',
BaseConfiguration.forwardButtonTooltipKey: 'Ir adelante',
BaseConfiguration.reloadButtonTooltipKey: 'Recargar',
BaseConfiguration.clearCacheButtonTooltipKey: 'Limpiar caché',
BaseConfiguration.closeButtonTooltipKey: 'Cerrar',
BaseConfiguration.clearCacheWarningMessageKey:
'¿Está seguro que desea limpiar la caché?',
},
contentLocale: Locale('es'),
refreshBtnVisible: false,
clearCacheBtnVisible: false,
onSuccessAuth: (credentials) {
isLoading = false;
setState(() {
authResponse = getPrettyCredentialsJson(credentials);
});
},
onError: (error) {
isLoading = false;
setState(() {
authResponse = error.toString();
});
},
onCancel: () {
isLoading = false;
setState(() {
authResponse = 'User cancelled authentication';
});
}),
);
}
- User is successfully redirected, which returns the full redirect url.
- An error occurred during navigation.
- User canceled web view.
This plugin offers two variants to handle these outputs.
Awaiting response from navigator route Future
.
void baseRedirectV1() async {
final result = await BaseWebScreen.start(
context: context,
configuration: BaseConfiguration(
initialUrl: initialUrl,
redirectUrls: [redirectUrl, baseUrl],
onCertificateValidate: (certificate) {
///This is recommended
/// Do certificate validations here
/// If false is returned then a CertificateException() will be thrown
return true;
},
textLocales: {
///Optionally texts can be localized
BaseConfiguration.backButtonTooltipKey: 'Ir atrás',
BaseConfiguration.forwardButtonTooltipKey: 'Ir adelante',
BaseConfiguration.reloadButtonTooltipKey: 'Recargar',
BaseConfiguration.clearCacheButtonTooltipKey: 'Limpiar caché',
BaseConfiguration.closeButtonTooltipKey: 'Cerrar',
BaseConfiguration.clearCacheWarningMessageKey:
'¿Está seguro que desea limpiar la caché?',
},
contentLocale: Locale('es'),
refreshBtnVisible: false,
clearCacheBtnVisible: false,
),
);
if (result != null) {
if (result is String) {
/// If result is String it means redirected successful
response = 'User redirected to: $result';
} else {
/// If result is not String then some error occurred
response = result.toString();
}
} else {
/// If no result means user cancelled
response = 'User cancelled';
}
setState(() {});
}
Using callbacks
void baseRedirectV2() {
BaseWebScreen.start(
context: context,
configuration: BaseConfiguration(
initialUrl: initialUrl,
redirectUrls: [redirectUrl, baseUrl],
onCertificateValidate: (certificate) {
///This is recommended
/// Do certificate validations here
/// If false is returned then a CertificateException() will be thrown
return true;
},
textLocales: {
///Optionally text can be localized
BaseConfiguration.backButtonTooltipKey: 'Ir atrás',
BaseConfiguration.forwardButtonTooltipKey: 'Ir adelante',
BaseConfiguration.reloadButtonTooltipKey: 'Recargar',
BaseConfiguration.clearCacheButtonTooltipKey: 'Limpiar caché',
BaseConfiguration.closeButtonTooltipKey: 'Cerrar',
BaseConfiguration.clearCacheWarningMessageKey:
'¿Está seguro que desea limpiar la caché?',
},
contentLocale: Locale('es'),
refreshBtnVisible: false,
clearCacheBtnVisible: false,
onSuccessRedirect: (responseRedirect) {
setState(() {
response = 'User redirected to: $responseRedirect';
});
},
onError: (error) {
setState(() {
response = error.toString();
});
},
onCancel: () {
setState(() {
response = 'User cancelled';
});
}),
);
}
goBackBtnVisible
,goForwardBtnVisible
,refreshBtnVisible
,clearCacheBtnVisible
,closeBtnVisible
allows you to show/hide buttons from toolbar, if you want to completely hide toolbar, set all buttons to false.- Use
urlStream
when you need to asynchronously navigate to a specific url, like when user registered usingOAuthWebAuth
and the web view waits for user email verification; in this case when the user opens the email verification link you can navigate to this link by emitting the new url to the stream you previously set in theurlStream
instead of creating a newOAuthWebAuth
orBaseWebView
. - You can clear cache, clear cookies or both directly from
OAuthWebAuth.instance
, likeOAuthWebAuth.instance.clearCache()
,OAuthWebAuth.instance.clearCookies()
orOAuthWebAuth.instance.clearAll()
. Important, it's recommended you setBuildContext
when using any of the clearCache functions to look up the proper FlutterView. - For more details on how to use check the sample project of this plugin.
- Keycloak
- Auth0
Note: It should work with any OAuth2 comatible service that uses Authorization Code Grant