diff --git a/README.md b/README.md index 70c1f68..6589969 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,15 @@ dependencies: aad_oauth: "^1.0.1" ``` +## Language support + +You can change your default user flow page language by defining: + +```dart +final AadOAuth oauth = new AadOAuth(config); +oauth.setLanguage('en') +``` + ## Contribution Contributions can be submitted as pull requests and are highly welcomed. Changes will be bundled together into a release. You can find the next release date and past releases in the [CHANGELOG file](CHANGELOG.md). diff --git a/assets/msalv2.js b/assets/msalv2.js index 73bc13e..d3307e6 100644 --- a/assets/msalv2.js +++ b/assets/msalv2.js @@ -12,47 +12,76 @@ var aadOauth = (function () { loginHint: null }; + function deriveAuthorityFromAuthorizationUrl(authorizationUrl) { + const oauthSuffixes = ['/oauth2/v2.0/authorize', '/oauth2/authorize']; + + for (const suffix of oauthSuffixes) { + if (authorizationUrl.endsWith(suffix)) { + let authority = authorizationUrl.substring(0, authorizationUrl.length - suffix.length); + if (!authority.endsWith('/')) { + authority += '/'; + } + return authority; + } + } + + return authorizationUrl.endsWith('/') ? authorizationUrl : authorizationUrl + '/'; + } + // Initialise the myMSALObj for the given client, authority and scope - function init(config) { - // TODO: Add support for other MSAL configuration - var authData = { - clientId: config.clientId, - authority: config.isB2C ? "https://" + config.tenant + ".b2clogin.com/tfp/" + config.tenant + ".onmicrosoft.com/" + config.policy + "/" : "https://login.microsoftonline.com/" + config.tenant, - knownAuthorities: [ config.tenant + ".b2clogin.com", "login.microsoftonline.com"], - redirectUri: config.redirectUri, - }; - var postLogoutRedirectUri = { - postLogoutRedirectUri: config.postLogoutRedirectUri, - }; - var msalConfig = { - auth: config?.postLogoutRedirectUri == null ? { - ...authData, - } : { - ...authData, - ...postLogoutRedirectUri, - }, - cache: { - cacheLocation: config.cacheLocation, - storeAuthStateInCookie: false, - }, - }; - - if (typeof config.scope === "string") { - tokenRequest.scopes = config.scope.split(" "); - } else { - tokenRequest.scopes = config.scope; - } - - tokenRequest.extraQueryParameters = JSON.parse(config.customParameters); - tokenRequest.prompt = config.prompt; - tokenRequest.loginHint = config.loginHint; - - myMSALObj = new msal.PublicClientApplication(msalConfig); - // Register Callbacks for Redirect flow and record the task so we - // can await its completion in the login API - - redirectHandlerTask = myMSALObj.handleRedirectPromise(); - } + function init(config) { + let authority; + if (config.customAuthorizationUrl) { + authority = deriveAuthorityFromAuthorizationUrl(config.customAuthorizationUrl); + } else { + authority = config.isB2C ? "https://" + config.tenant + ".b2clogin.com/tfp/" + config.tenant + ".onmicrosoft.com/" + config.policy + "/" : "https://login.microsoftonline.com/" + config.tenant; + } + + const isCustomDomain = !authority.includes('microsoftonline.com') && + !authority.includes('b2clogin.com'); + + const knownAuthorities = isCustomDomain + ? [new URL(authority).host] + : [config.tenant + ".b2clogin.com", "login.microsoftonline.com"]; + + var authData = { + clientId: config.clientId, + authority: authority, + knownAuthorities: knownAuthorities, + redirectUri: config.redirectUri, + }; + var postLogoutRedirectUri = { + postLogoutRedirectUri: config.postLogoutRedirectUri, + }; + var msalConfig = { + auth: config?.postLogoutRedirectUri == null ? { + ...authData, + } : { + ...authData, + ...postLogoutRedirectUri, + }, + cache: { + cacheLocation: config.cacheLocation, + storeAuthStateInCookie: false, + }, + }; + + if (typeof config.scope === "string") { + tokenRequest.scopes = config.scope.split(" "); + } else { + tokenRequest.scopes = config.scope; + } + + tokenRequest.extraQueryParameters = JSON.parse(config.customParameters); + tokenRequest.prompt = config.prompt; + tokenRequest.loginHint = config.loginHint; + + myMSALObj = new msal.PublicClientApplication(msalConfig); + // Register Callbacks for Redirect flow and record the task so we + // can await its completion in the login API + + redirectHandlerTask = myMSALObj.handleRedirectPromise(); + } // Tries to silently acquire a token. Will return null if a token // could not be acquired or if no cached account credentials exist. diff --git a/lib/aad_oauth.dart b/lib/aad_oauth.dart index 4cd9ead..1543e6d 100644 --- a/lib/aad_oauth.dart +++ b/lib/aad_oauth.dart @@ -12,9 +12,27 @@ import 'model/config.dart'; /// Authenticates a user with Azure Active Directory using OAuth2.0. class AadOAuth { - final CoreOAuth _coreOAuth; + CoreOAuth _coreOAuth; + Config _config; - AadOAuth(Config config) : _coreOAuth = CoreOAuth.fromConfig(config); + AadOAuth(Config config) + : _coreOAuth = CoreOAuth.fromConfig(config), + _config = config; + + /// Changes dynamically language displayed in the login/register form + void setLanguage(String languageCode) { + const localesKey = 'ui_locales'; + + final updatedParameters = Map.of(_config.customParameters); + updatedParameters[localesKey] = languageCode; + + final updatedConfig = _config.copyWith( + customParameters: updatedParameters, + ); + + _config = updatedConfig; + _coreOAuth = CoreOAuth.fromConfig(_config); + } /// Perform Azure AD login. /// diff --git a/lib/helper/auth_storage.dart b/lib/helper/auth_storage.dart index 54a184e..093a53c 100644 --- a/lib/helper/auth_storage.dart +++ b/lib/helper/auth_storage.dart @@ -8,10 +8,13 @@ class AuthStorage { final String _tokenIdentifier; final Token emptyToken = Token(); - AuthStorage( - {String tokenIdentifier = 'Token', required AndroidOptions aOptions}) - : _tokenIdentifier = tokenIdentifier, - _secureStorage = FlutterSecureStorage(aOptions: aOptions); + AuthStorage({ + String tokenIdentifier = 'Token', + required AndroidOptions aOptions, + required IOSOptions iOptions, + }) : _tokenIdentifier = tokenIdentifier, + _secureStorage = + FlutterSecureStorage(aOptions: aOptions, iOptions: iOptions); Future saveTokenToCache(Token token) async { var data = Token.toJsonMap(token); diff --git a/lib/helper/mobile_oauth.dart b/lib/helper/mobile_oauth.dart index 01a6f0f..c4bd4eb 100644 --- a/lib/helper/mobile_oauth.dart +++ b/lib/helper/mobile_oauth.dart @@ -22,9 +22,9 @@ class MobileOAuth extends CoreOAuth { /// [config] Parameters according to official Microsoft Documentation. MobileOAuth(Config config) : _authStorage = AuthStorage( - tokenIdentifier: config.tokenIdentifier, - aOptions: config.aOptions, - ), + tokenIdentifier: config.tokenIdentifier, + aOptions: config.aOptions, + iOptions: config.iOptions), _requestCode = RequestCode(config), _requestToken = RequestToken(config); diff --git a/lib/model/config.dart b/lib/model/config.dart index 6c38767..d77015e 100644 --- a/lib/model/config.dart +++ b/lib/model/config.dart @@ -139,6 +139,9 @@ class Config { /// android storage options for shared preferences - defaults to encrypting shared prefs AndroidOptions aOptions; + /// ios storage options for secure storage + IOSOptions iOptions; + /// Cache location used when authenticating with a web client. /// "CacheLocation.localStorage" - Local browser storage (default) /// "CacheLocation.sessionStorage" - Session context @@ -217,6 +220,7 @@ class Config { this.isStub = false, this.loader = const SizedBox(), AndroidOptions? aOptions, + this.iOptions = IOSOptions.defaultOptions, CacheLocation? cacheLocation, required this.navigatorKey, this.origin, @@ -269,6 +273,7 @@ class Config { bool? isStub, Widget? loader, AndroidOptions? aOptions, + IOSOptions? iOptions, CacheLocation? cacheLocation, GlobalKey? navigatorKey, String? origin, @@ -307,6 +312,7 @@ class Config { isStub: isStub ?? this.isStub, loader: loader ?? this.loader, aOptions: aOptions ?? this.aOptions, + iOptions: iOptions ?? this.iOptions, cacheLocation: cacheLocation ?? this.cacheLocation, navigatorKey: navigatorKey ?? this.navigatorKey, origin: origin ?? this.origin, diff --git a/pubspec.yaml b/pubspec.yaml index ccd6e2e..a9f3dd6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,13 +11,13 @@ environment: dependencies: flutter: sdk: flutter - flutter_secure_storage: ^9.0.0 - webview_flutter: ^4.4.1 + flutter_secure_storage: ^9.2.4 + webview_flutter: ^4.13.0 js: ^0.6.7 - http: ^1.1.0 - shared_preferences: ^2.2.1 + http: ^1.5.0 + shared_preferences: ^2.5.3 dartz: ^0.10.1 - equatable: ^2.0.5 + equatable: ^2.0.7 dev_dependencies: flutter_test: