From 282c364e28c1e1f791f83e2f49e14899092c8c62 Mon Sep 17 00:00:00 2001 From: Bartosz Wielek Date: Wed, 18 Sep 2024 07:21:26 +0200 Subject: [PATCH 1/8] done --- lib/aad_oauth.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) 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. /// From 98822f436770257d25dc7f7028c632d96f071b5d Mon Sep 17 00:00:00 2001 From: Bartosz Wielek Date: Wed, 18 Sep 2024 07:30:18 +0200 Subject: [PATCH 2/8] added readme --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) 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). From b6634595ff11a2be58098c83dc8b7ae35b6f8343 Mon Sep 17 00:00:00 2001 From: Kevin Strathdee Date: Mon, 11 Aug 2025 13:12:17 +0200 Subject: [PATCH 3/8] feat: add support for custom MSAL configs --- assets/msalv2.js | 109 ++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 40 deletions(-) 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. From fc20be8e5347e2ebd3100b99238413af609544b4 Mon Sep 17 00:00:00 2001 From: Bartosz Wielek Date: Wed, 1 Oct 2025 10:42:24 +0200 Subject: [PATCH 4/8] upgrade dependencies --- pubspec.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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: From af9e81c7380e344dc50a3283636bc045dda30a32 Mon Sep 17 00:00:00 2001 From: Kevin Strathdee Date: Mon, 3 Nov 2025 09:32:25 +0100 Subject: [PATCH 5/8] feat: add option to provide custom ios options for auth storage --- lib/helper/auth_storage.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/helper/auth_storage.dart b/lib/helper/auth_storage.dart index 54a184e..db2b21e 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, + IOSOptions iOptions = IOSOptions.defaultOptions, + }) : _tokenIdentifier = tokenIdentifier, + _secureStorage = + FlutterSecureStorage(aOptions: aOptions, iOptions: iOptions); Future saveTokenToCache(Token token) async { var data = Token.toJsonMap(token); From f88e15c9ced0877df8a1d31d96056f1e6ce758b5 Mon Sep 17 00:00:00 2001 From: Kevin Strathdee Date: Mon, 3 Nov 2025 09:46:59 +0100 Subject: [PATCH 6/8] feat: add iOptions to config --- lib/helper/auth_storage.dart | 2 +- lib/helper/mobile_oauth.dart | 6 +++--- lib/model/config.dart | 31 +++++++++++++++++-------------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/helper/auth_storage.dart b/lib/helper/auth_storage.dart index db2b21e..093a53c 100644 --- a/lib/helper/auth_storage.dart +++ b/lib/helper/auth_storage.dart @@ -11,7 +11,7 @@ class AuthStorage { AuthStorage({ String tokenIdentifier = 'Token', required AndroidOptions aOptions, - IOSOptions iOptions = IOSOptions.defaultOptions, + required IOSOptions iOptions, }) : _tokenIdentifier = tokenIdentifier, _secureStorage = FlutterSecureStorage(aOptions: aOptions, iOptions: iOptions); 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..8c07dab 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, @@ -224,18 +228,17 @@ class Config { this.postLogoutRedirectUri, this.appBar, this.onPageFinished, - }) - : authorizationUrl = customAuthorizationUrl ?? - (isB2C - ? (customDomainUrlWithTenantId == null - ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/authorize' - : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/authorize') - : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/authorize'), + }) : authorizationUrl = customAuthorizationUrl ?? + (isB2C + ? (customDomainUrlWithTenantId == null + ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/authorize' + : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/authorize') + : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/authorize'), tokenUrl = customTokenUrl ?? (isB2C ? (customDomainUrlWithTenantId == null - ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/token' - : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/token') + ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/token' + : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/token') : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/token'), aOptions = aOptions ?? AndroidOptions(encryptedSharedPreferences: true), cacheLocation = cacheLocation ?? CacheLocation.localStorage, @@ -295,11 +298,11 @@ class Config { clientSecret: clientSecret ?? this.clientSecret, resource: resource ?? this.resource, isB2C: isB2C ?? this.isB2C, - customAuthorizationUrl: customAuthorizationUrl ?? - this.customAuthorizationUrl, + customAuthorizationUrl: + customAuthorizationUrl ?? this.customAuthorizationUrl, customTokenUrl: customTokenUrl ?? this.customTokenUrl, - customDomainUrlWithTenantId: customDomainUrlWithTenantId ?? - this.customDomainUrlWithTenantId, + customDomainUrlWithTenantId: + customDomainUrlWithTenantId ?? this.customDomainUrlWithTenantId, loginHint: loginHint ?? this.loginHint, domainHint: domainHint ?? this.domainHint, codeVerifier: codeVerifier ?? this.codeVerifier, @@ -312,7 +315,7 @@ class Config { origin: origin ?? this.origin, customParameters: customParameters ?? this.customParameters, postLogoutRedirectUri: - postLogoutRedirectUri ?? this.postLogoutRedirectUri, + postLogoutRedirectUri ?? this.postLogoutRedirectUri, appBar: appBar ?? this.appBar, onPageFinished: onPageFinished ?? this.onPageFinished, ); From 1c6d6d966cd7d1f53d2eb37d6d6069319d19dd7b Mon Sep 17 00:00:00 2001 From: Kevin Strathdee Date: Mon, 3 Nov 2025 09:50:36 +0100 Subject: [PATCH 7/8] chore: revert auto-formatting --- lib/model/config.dart | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/model/config.dart b/lib/model/config.dart index 8c07dab..1cfd601 100644 --- a/lib/model/config.dart +++ b/lib/model/config.dart @@ -228,17 +228,18 @@ class Config { this.postLogoutRedirectUri, this.appBar, this.onPageFinished, - }) : authorizationUrl = customAuthorizationUrl ?? - (isB2C - ? (customDomainUrlWithTenantId == null - ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/authorize' - : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/authorize') - : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/authorize'), + }) + : authorizationUrl = customAuthorizationUrl ?? + (isB2C + ? (customDomainUrlWithTenantId == null + ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/authorize' + : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/authorize') + : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/authorize'), tokenUrl = customTokenUrl ?? (isB2C ? (customDomainUrlWithTenantId == null - ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/token' - : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/token') + ? 'https://$tenant.b2clogin.com/$tenant.onmicrosoft.com/$policy/oauth2/v2.0/token' + : '$customDomainUrlWithTenantId/$policy/oauth2/v2.0/token') : 'https://login.microsoftonline.com/$tenant/oauth2/v2.0/token'), aOptions = aOptions ?? AndroidOptions(encryptedSharedPreferences: true), cacheLocation = cacheLocation ?? CacheLocation.localStorage, @@ -298,11 +299,11 @@ class Config { clientSecret: clientSecret ?? this.clientSecret, resource: resource ?? this.resource, isB2C: isB2C ?? this.isB2C, - customAuthorizationUrl: - customAuthorizationUrl ?? this.customAuthorizationUrl, + customAuthorizationUrl: customAuthorizationUrl ?? + this.customAuthorizationUrl, customTokenUrl: customTokenUrl ?? this.customTokenUrl, - customDomainUrlWithTenantId: - customDomainUrlWithTenantId ?? this.customDomainUrlWithTenantId, + customDomainUrlWithTenantId: customDomainUrlWithTenantId ?? + this.customDomainUrlWithTenantId, loginHint: loginHint ?? this.loginHint, domainHint: domainHint ?? this.domainHint, codeVerifier: codeVerifier ?? this.codeVerifier, @@ -315,7 +316,7 @@ class Config { origin: origin ?? this.origin, customParameters: customParameters ?? this.customParameters, postLogoutRedirectUri: - postLogoutRedirectUri ?? this.postLogoutRedirectUri, + postLogoutRedirectUri ?? this.postLogoutRedirectUri, appBar: appBar ?? this.appBar, onPageFinished: onPageFinished ?? this.onPageFinished, ); From 9b39ead638e1f2f27c15b5490881f6f8c6bb6268 Mon Sep 17 00:00:00 2001 From: Kevin Strathdee Date: Mon, 3 Nov 2025 09:52:33 +0100 Subject: [PATCH 8/8] fix: add iOptions to copyWith --- lib/model/config.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/model/config.dart b/lib/model/config.dart index 1cfd601..d77015e 100644 --- a/lib/model/config.dart +++ b/lib/model/config.dart @@ -273,6 +273,7 @@ class Config { bool? isStub, Widget? loader, AndroidOptions? aOptions, + IOSOptions? iOptions, CacheLocation? cacheLocation, GlobalKey? navigatorKey, String? origin, @@ -311,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,