Skip to content
Open
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
109 changes: 69 additions & 40 deletions assets/msalv2.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 20 additions & 2 deletions lib/aad_oauth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
11 changes: 7 additions & 4 deletions lib/helper/auth_storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> saveTokenToCache(Token token) async {
var data = Token.toJsonMap(token);
Expand Down
6 changes: 3 additions & 3 deletions lib/helper/mobile_oauth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
6 changes: 6 additions & 0 deletions lib/model/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -269,6 +273,7 @@ class Config {
bool? isStub,
Widget? loader,
AndroidOptions? aOptions,
IOSOptions? iOptions,
CacheLocation? cacheLocation,
GlobalKey<NavigatorState>? navigatorKey,
String? origin,
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down