diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcbf65a..6baa952 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## 1.0.0+2
+# 1.0.0+2
* Updates to readme in regards to kotlin static field issues.
## 1.0.0+1
* Added some more information to readme for clarity
diff --git a/README.md b/README.md
index deb6bc3..8abfee0 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ e.g. `https://msalfluttertest.b2clogin.com/tfp/msalfluttertest.onmicrosoft.com/B
For troubleshooting known bugs in the new build, please scroll down to the bottom of the page where all bugs and fixes we find will be noted.
-# MSAL Wrapper Library for Flutter
+## MSAL Wrapper Library for Flutter
Please note this product is in very early alpha release and subject to change and bugs.
The Microsoft Authentication Library Flutter Wrapper is a wrapper that uses that MSAL libraries for Android and IOS. Currently only the public client application functionality is supported, using the implicit workflow.
@@ -37,6 +37,7 @@ This section is mostly copied and modified from [the official android MSAL libra
```
+
```
2. In your AndroidManifest.xml file add the following intent filter, replacing the placeholder \ for your azure b2c application's client id where indicated below.
@@ -53,6 +54,7 @@ The default redirect url is msal\://auth however this can now b
android:host="auth" />
+
```
3. Copy the [msal_default_config](https://raw.githubusercontent.com/moodio/msal-flutter/master/doc/templates/msal_default_config.json) from this repository (or make your own if you know what you're doing) and place it into your flutter apps android/src/main/res/raw folder.
@@ -80,6 +82,7 @@ This section is mostly copied and modified from Step 1 from [the official iOS MS
+
```
2. Add LSApplicationQueriesSchemes to allow making call to Microsoft Authenticator if installed (For Authentication broker)
@@ -90,6 +93,7 @@ This section is mostly copied and modified from Step 1 from [the official iOS MS
msauthv2
msauthv3
+
```
3. Open the app's iOS project in xcode, click on the Runner app to open up the configuration, and under capabilities, expand Keychain Sharing and add the keychain group `com.microsoft.adalcache`
@@ -143,6 +147,7 @@ try{
} on MsalException {
//error handling logic here
}
+
```
4. Once a user has logged in atleast once, to retrieve a token silently call the acquireTokenSilent function, passing the scopes you wish to acquire the token for. Note that this function will throw an error on failure and should be surrounded by a try catch block as per the example below
@@ -184,6 +189,9 @@ try{
# Trouble Shooting
-Please note there is currently an issue that seems to occur with Android which uses slightly older versions of kotlin.
-If you get the error when attemtping to acquire a token, along the lines of "static member msalApp not found", goto your app's android folder, open the build.gradle file, and on the second line change the version of kotlin from 1.3.10 to 1.3.50. For more information take a look at issue #4.
+Please note there is currently an issue that seems to occur with Android which uses slightly older
+ versions of kotlin.
+If you get the error when attemtping to acquire a token, along the lines of "static member msalApp
+not found", goto your app's android folder, open the build.gradle file, and on the second line
+change the version of kotlin from 1.3.10 to 1.3.50. For more information take a look at issue #4.
A fix will be implemented shortly.
\ No newline at end of file
diff --git a/android/build.gradle b/android/build.gradle
index a844df8..1488f2c 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -18,6 +18,11 @@ rootProject.allprojects {
repositories {
google()
jcenter()
+ mavenCentral()
+ mavenLocal()
+ maven {
+ url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1'
+ }
}
}
@@ -26,22 +31,24 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 28
+ compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
+
defaultConfig {
- minSdkVersion 21
+ targetSdkVersion 30
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [msal_clientid: "5913dfb1-7576-451c-a7ea-a7c5a3f8682a"]
}
lintOptions {
disable 'InvalidPackage'
}
+
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'com.microsoft.identity.client:msal:1.0.+'
+ implementation 'com.microsoft.identity.client:msal:2.0.2'
}
diff --git a/android/src/main/kotlin/uk/co/moodio/msal_flutter/MsalFlutterPlugin.kt b/android/src/main/kotlin/uk/co/moodio/msal_flutter/MsalFlutterPlugin.kt
index c52dd26..b5fb084 100644
--- a/android/src/main/kotlin/uk/co/moodio/msal_flutter/MsalFlutterPlugin.kt
+++ b/android/src/main/kotlin/uk/co/moodio/msal_flutter/MsalFlutterPlugin.kt
@@ -4,58 +4,53 @@ import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.util.Log
-import androidx.annotation.WorkerThread
+import com.microsoft.identity.client.*
+import com.microsoft.identity.client.IPublicClientApplication.IMultipleAccountApplicationCreatedListener
+import com.microsoft.identity.client.IPublicClientApplication.LoadAccountsCallback
+import com.microsoft.identity.client.exception.MsalClientException
+import com.microsoft.identity.client.exception.MsalException
+import com.microsoft.identity.client.exception.MsalServiceException
+import com.microsoft.identity.client.exception.MsalUiRequiredException
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
-import com.microsoft.identity.client.*
-import com.microsoft.identity.client.exception.MsalException
-import com.microsoft.identity.client.IPublicClientApplication
-import com.microsoft.identity.client.PublicClientApplicationConfigurationFactory.initializeConfiguration
@Suppress("SpellCheckingInspection")
-class MsalFlutterPlugin: MethodCallHandler {
- companion object
- {
- lateinit var mainActivity : Activity
+class MsalFlutterPlugin : MethodCallHandler {
+ companion object {
+ lateinit var mainActivity: Activity
lateinit var msalApp: IMultipleAccountPublicClientApplication
+ lateinit var accountList: List
fun isClientInitialized() = ::msalApp.isInitialized
@JvmStatic
fun registerWith(registrar: Registrar) {
- Log.d("MsalFlutter","Registering plugin")
+ // Log.d("MsalFlutter","Registering plugin")
val channel = MethodChannel(registrar.messenger(), "msal_flutter")
channel.setMethodCallHandler(MsalFlutterPlugin())
mainActivity = registrar.activity()
}
- fun getAuthCallback(result: Result) : AuthenticationCallback
- {
- Log.d("MsalFlutter", "Getting the auth callback object")
- return object : AuthenticationCallback
- {
- override fun onSuccess(authenticationResult : IAuthenticationResult){
- Log.d("MsalFlutter", "Authentication successful")
+ fun getAuthCallback(result: Result): AuthenticationCallback {
+
+ return object : AuthenticationCallback {
+ override fun onSuccess(authenticationResult: IAuthenticationResult) {
Handler(Looper.getMainLooper()).post {
result.success(authenticationResult.accessToken)
}
}
- override fun onError(exception : MsalException)
- {
- Log.d("MsalFlutter","Error logging in!")
- Log.d("MsalFlutter", exception.message)
+ override fun onError(exception: MsalException) {
Handler(Looper.getMainLooper()).post {
result.error("AUTH_ERROR", "Authentication failed", exception.localizedMessage)
}
}
- override fun onCancel(){
- Log.d("MsalFlutter", "Cancelled")
+ override fun onCancel() {
Handler(Looper.getMainLooper()).post {
result.error("CANCELLED", "User cancelled", null)
}
@@ -63,15 +58,40 @@ class MsalFlutterPlugin: MethodCallHandler {
}
}
- private fun getApplicationCreatedListener(result: Result) : IPublicClientApplication.ApplicationCreatedListener {
- Log.d("MsalFlutter", "Getting the created listener")
+ /**
+ * Callback used in for silent acquireToken calls.
+ */
+ fun getAuthSilentCallback(result: Result): SilentAuthenticationCallback {
+ return object : SilentAuthenticationCallback {
+ override fun onSuccess(authenticationResult: IAuthenticationResult) {
+ Log.d("MSAL_FLUTTER", "Successfully authenticated")
+ Handler(Looper.getMainLooper()).post {
+ result.success(authenticationResult.accessToken)
+ }
+ }
+
+ override fun onError(exception: MsalException) {
+ /* Failed to acquireToken */
+ Log.d("MSAL_FLUTTER", "Authentication failed: $exception")
+ if (exception is MsalClientException) {
+ result.error("NO_SCOPE", "Call must include a scope", exception.localizedMessage)
+ } else if (exception is MsalServiceException) {
+ result.error("NO_SCOPE", exception.localizedMessage, exception.localizedMessage)
+ } else if (exception is MsalUiRequiredException) {
+ result.error("NO_SCOPE", "Call must include a scope", exception.localizedMessage)
+ }
+ }
+ }
+ }
+
+ private fun getApplicationCreatedListener(result: Result): IMultipleAccountApplicationCreatedListener {
+
+ return object : IMultipleAccountApplicationCreatedListener {
+ override fun onCreated(application: IMultipleAccountPublicClientApplication) {
+
+ msalApp = application
+ result.success(true)
- return object : IPublicClientApplication.ApplicationCreatedListener
- {
- override fun onCreated(application: IPublicClientApplication) {
- Log.d("MsalFlutter", "Created successfully")
- msalApp = application as MultipleAccountPublicClientApplication
- result.success(true)
}
override fun onError(exception: MsalException?) {
@@ -80,51 +100,43 @@ class MsalFlutterPlugin: MethodCallHandler {
}
}
}
+
+
}
- override fun onMethodCall(call: MethodCall, result: Result)
- {
- val scopesArg : ArrayList? = call.argument("scopes")
+ override fun onMethodCall(call: MethodCall, result: Result) {
+ val scopesArg: ArrayList? = call.argument("scopes")
val scopes: Array? = scopesArg?.toTypedArray()
- val clientId : String? = call.argument("clientId")
- val authority : String? = call.argument("authority")
+ val clientId: String? = call.argument("clientId")
+ val authority: String? = call.argument("authority")
- Log.d("MsalFlutter","Got scopes: $scopes")
- Log.d("MsalFlutter","Got cleintId: $clientId")
- Log.d("MsalFlutter","Got authority: $authority")
-
- when(call.method){
- "logout" -> Thread(Runnable{logout(result)}).start()
+ when (call.method) {
+ "logout" -> Thread(Runnable { logout(result) }).start()
"initialize" -> initialize(clientId, authority, result)
- "acquireToken" -> Thread(Runnable {acquireToken(scopes, result)}).start()
- "acquireTokenSilent" -> Thread(Runnable {acquireTokenSilent(scopes, result)}).start()
+ "loadAccounts" -> Thread(Runnable { loadAccounts(result) }).start()
+ "acquireToken" -> Thread(Runnable { acquireToken(scopes, result) }).start()
+ "acquireTokenSilent" -> Thread(Runnable { acquireTokenSilent(scopes, result) }).start()
else -> result.notImplemented()
}
}
- private fun acquireToken(scopes : Array?, result: Result)
- {
- Log.d("MsalFlutter", "acquire token called")
-
+ private fun acquireToken(scopes: Array?, result: Result) {
// check if client has been initialized
- if(!isClientInitialized()){
- Log.d("MsalFlutter","Client has not been initialized")
+ if (!isClientInitialized()) {
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check scopes
- if(scopes == null){
- Log.d("MsalFlutter", "no scope")
+ if (scopes == null) {
result.error("NO_SCOPE", "Call must include a scope", null)
return
}
//remove old accounts
- while(msalApp.accounts.any()){
- Log.d("MsalFlutter","Removing old account")
+ while (msalApp.accounts.any()) {
msalApp.removeAccount(msalApp.accounts.first())
}
@@ -132,21 +144,17 @@ class MsalFlutterPlugin: MethodCallHandler {
msalApp.acquireToken(mainActivity, scopes, getAuthCallback(result))
}
- private fun acquireTokenSilent(scopes : Array?, result: Result)
- {
- Log.d("MsalFlutter", "Called acquire token silent")
-
+ private fun acquireTokenSilent(scopes: Array?, result: Result) {
// check if client has been initialized
- if(!isClientInitialized()){
- Log.d("MsalFlutter","Client has not been initialized")
+
+ if (!isClientInitialized()) {
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check the scopes
- if(scopes == null){
- Log.d("MsalFlutter", "no scope")
+ if (scopes == null) {
Handler(Looper.getMainLooper()).post {
result.error("NO_SCOPE", "Call must include a scope", null)
}
@@ -154,59 +162,89 @@ class MsalFlutterPlugin: MethodCallHandler {
}
//ensure accounts exist
- if(msalApp.accounts.isEmpty()){
+ if (accountList?.isEmpty()) {
Handler(Looper.getMainLooper()).post {
result.error("NO_ACCOUNT", "No account is available to acquire token silently for", null)
}
return
}
-
+ val selectedAccount: IAccount = accountList.first();
//acquire the token and return the result
- val res = msalApp.acquireTokenSilent(scopes, msalApp.accounts[0], msalApp.configuration.defaultAuthority.authorityURL.toString())
- Handler(Looper.getMainLooper()).post {
- result.success(res.accessToken)
- }
+ val sc = scopes.map { s -> s.toLowerCase() }.toTypedArray()
+
+ msalApp.acquireTokenSilentAsync(sc, selectedAccount, selectedAccount.authority, getAuthSilentCallback(result))
+
}
- private fun initialize(clientId: String?, authority: String?, result: Result)
- {
+
+ private fun initialize(clientId: String?, authority: String?, result: Result) {
//ensure clientid provided
- if(clientId == null){
- Log.d("MsalFlutter","error no clientId")
+ if (clientId == null) {
result.error("NO_CLIENTID", "Call must include a clientId", null)
return
}
//if already initialized, ensure clientid hasn't changed
- if(isClientInitialized()){
- Log.d("MsalFlutter","Client already initialized.")
- if(msalApp.configuration.clientId == clientId)
- {
+ if (isClientInitialized()) {
+ if (msalApp.configuration.clientId == clientId) {
result.success(true)
} else {
result.error("CHANGED_CLIENTID", "Attempting to initialize with multiple clientIds.", null)
}
}
-
- // if authority is set, create client using it, otherwise use default
- if(authority != null){
- Log.d("MsalFlutter", "Authority not null")
- Log.d("MsalFlutter", "Creating with: $clientId - $authority")
- PublicClientApplication.create(mainActivity.applicationContext, clientId, authority, getApplicationCreatedListener(result))
- }else{
- Log.d("MsalFlutter", "Authority null")
- PublicClientApplication.create(mainActivity.applicationContext, clientId, getApplicationCreatedListener(result))
+ if(!isClientInitialized()) {
+ // if authority is set, create client using it, otherwise use default
+ PublicClientApplication.createMultipleAccountPublicClientApplication(mainActivity.applicationContext,
+ R.raw.msal_default_config, getApplicationCreatedListener(result))
}
+
}
+ /**
+ * Load currently signed-in accounts, if there's any.
+ */
+ private fun loadAccounts(result: Result) {
- private fun logout(result: Result){
- while(msalApp.accounts.any()){
- Log.d("MsalFlutter","Removing old account")
- msalApp.removeAccount(msalApp.accounts.first())
+ msalApp.getAccounts(object : LoadAccountsCallback {
+
+ override fun onTaskCompleted(resultList: List) {
+ accountList = resultList
+ result.success(true)
+ }
+
+ override fun onError(exception: MsalException) {
+ result.error("NO_ACCOUNT", "No account is available to acquire token silently for", exception)
+ }
+ })
+ }
+
+
+ private fun logout(result: Result) {
+ if(!isClientInitialized()){
+ Handler(Looper.getMainLooper()).post {
+ result.error("NO_ACCOUNT", "No account is available to acquire token silently for", null)
+ }
+ return
}
- Handler(Looper.getMainLooper()).post {
- result.success(true)
+
+ if (accountList?.isEmpty()) {
+ Handler(Looper.getMainLooper()).post {
+ result.error("NO_ACCOUNT", "No account is available to acquire token silently for", null)
+ }
+ return
}
+
+ msalApp.removeAccount(accountList.first(), object : IMultipleAccountPublicClientApplication.RemoveAccountCallback{
+ override fun onRemoved() {
+ Thread(Runnable { loadAccounts(result) }).start()
+ }
+
+ override fun onError(exception: MsalException) {
+ result.error("NO_ACCOUNT", "No account is available to acquire token silently for", exception)
+ }
+ })
+
}
}
+
+
diff --git a/example/.flutter-plugins-dependencies b/example/.flutter-plugins-dependencies
new file mode 100644
index 0000000..13f0a16
--- /dev/null
+++ b/example/.flutter-plugins-dependencies
@@ -0,0 +1 @@
+{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"msal_flutter","path":"/Users/fabioconceicao/Documents/Folder Projects/msal-flutter/","dependencies":[]}],"android":[{"name":"msal_flutter","path":"/Users/fabioconceicao/Documents/Folder Projects/msal-flutter/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"msal_flutter","dependencies":[]}],"date_created":"2020-11-11 08:48:46.235613","version":"1.22.3"}
\ No newline at end of file
diff --git a/example/android/app/Keystore/debug.keystore b/example/android/app/Keystore/debug.keystore
new file mode 100644
index 0000000..cbcc215
Binary files /dev/null and b/example/android/app/Keystore/debug.keystore differ
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index b06baf9..26dc2a3 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 28
+ compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -38,9 +38,10 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+
applicationId "uk.co.moodio.msal_flutter_example"
minSdkVersion 21
- targetSdkVersion 28
+ targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,9 +51,24 @@ android {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
+ buildConfigField "boolean", 'IS_PRINT_LOG', "false"
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ signingConfig signingConfigs.debug
+ }
+
+ debug {
signingConfig signingConfigs.debug
}
}
+
+ signingConfigs {
+ debug {
+ storeFile file("Keystore/debug.keystore")
+ storePassword 'android'
+ keyAlias 'androiddebugkey'
+ keyPassword 'android'
+ }
+ }
}
flutter {
@@ -61,7 +77,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'androidx.test:runner:1.1.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
+ testImplementation 'junit:junit:4.13'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index af28b19..cd515f2 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
${e.message} --> ${e.code}");
throw _convertException(e);
}
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 2a3903c..e6f62b0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,8 @@
name: msal_flutter
description: A Microsoft Authentication Library wrapper for Android and iOS
-version: 1.0.0+2
-homepage: https://github.com/moodio/msal-flutter
-repository: https://github.com/moodio/msal-flutter
+version: 1.0.0+4
+#homepage: https://github.com/moodio/msal-flutter
+#repository: https://github.com/moodio/msal-flutter
environment:
sdk: ">=2.1.0 <3.0.0"