Skip to content

Commit

Permalink
Merge pull request #1910 from AzureAD/dev-react-native-poc
Browse files Browse the repository at this point in the history
Merge MSAL React Native Proof of Concept Library and Sample
  • Loading branch information
Melissa Ahn committed Jul 16, 2020
2 parents 4b84a76 + ab72b28 commit d77aafd
Show file tree
Hide file tree
Showing 80 changed files with 19,676 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Expand Up @@ -11,5 +11,6 @@
"github-pr.targetBranch": "dev",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
},
"java.configuration.updateBuildConfiguration": "interactive"
}
22 changes: 22 additions & 0 deletions lib/msal-react-native-android-poc/.gitignore
@@ -0,0 +1,22 @@
# OSX
#
.DS_Store

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml

# BUCK
buck-out/
\.buckd/
*.keystore
1 change: 1 addition & 0 deletions lib/msal-react-native-android-poc/.npmrc
@@ -0,0 +1 @@
registry=https://npm.pkg.github.com/AzureAD
98 changes: 98 additions & 0 deletions lib/msal-react-native-android-poc/README.md
@@ -0,0 +1,98 @@
# MSAL React Native Proof of Concept (Android)
**Note: With this library being a proof of concept, it is not recommended to be used for React Native applications intended for production.**

MSAL for React Native is a proof of concept that allows React Native applications to authenticate users with Microsoft work and school accounts (AAD) as well as acquire access tokens from the Microsoft identity platform endpoint in order to access secured web APIs. Currently, this library is only available for the Android platform.
## Getting started

### Installation

`$ npm install @azuread/msal-react-native-android-poc`

### Register your application in the Azure Portal
Under Authentication -> Platform configurations, click "Add a platform" and select Android.
To generate the base64 signature of your application:
* MacOS: `keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64`
* Windows: `keytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%.android\debug.keystore | openssl sha1 -binary | openssl base64`

For more information and help, refer to this [MSAL FAQ](https://github.com/AzureAD/microsoft-authentication-library-for-android/wiki/MSAL-FAQ).

### Configuring in app
Follow steps 1 through 3 in the [Using MSAL](https://github.com/AzureAD/microsoft-authentication-library-for-android#using-msal) section of MSAL for Android's README with these modifications/reminders:
* In step 2, please name the config file `auth_config_single_account.json` and put it in `res/raw`. You can pretty much copy and paste the MSAL configuration generated for you in Azure Portal and refer to the [configuration file documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-configuration).
* In step 3, remember that for `android:path` you want to paste the base64 signature hash you generated in the console and not the url encoded version.

## Usage
Import MSAL for React Native as `MSAL`:
```javascript
import MSAL from '@azuread/msal-react-native-android-poc';
```
Importing will automatically create a `SingleAccountPublicClientApplication` with the configurations provided in `auth_config_single_account.json`.
All of the MSAL methods return a promise.
You can use `MSAL` to sign in a user with [scopes](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#scopes-and-permissions) (a string of scopes separated by " "):
```javascript
try {
await MSAL.signIn(scopesValue);
} catch (error) {
console.log(error);
}
```
Most of the methods return a map, and in the case of `signIn`, a map containing the variables of an `IAccount` object is returned.
We can access the username of the currently signed in user and log it to console (for example):
```javascript
try
const account = await MSAL.signIn(scopesValue);
console.log("Username: " + account.username)
} catch (error) {
console.log(error);
}
```
Since a user is now signed in, we can try to acquire an access token silently and make a call to Microsoft Graph API:
```javascript
try {
const result = await MSAL.acquireTokenSilent(scopesValue);
//api call to MSGraph
const response = await fetch (
"https://graph.microsoft.com/v1.0/me", {
method: 'GET',
headers: {
Authorization: `Bearer ${result.accessToken}`
}
}
);

... //do something with response here

} catch (error) {
console.log(error);
}
```
Please see msal-react-native-android-poc-sample in the sample folder for an example app that demonstrates the MSAL methods.

For a complete listing of each method's return value and map keys, see the Methods(link to Methods) section.

## What to expect from this library
Because this library is a proof of concept, it is scoped to specific features and does not implement all the capabilities of an official MSAL for React Native library.

This proof of concept is scoped to consider:
* Compatibility with the Android platform
* Authenticating users with work and school Azure AD accounts (AAD)
* Single-account mode

This proof of concept does not implement:
* Compatibility with iOS
* To allow compatibility with iOS, an [Objective-C native module](https://reactnative.dev/docs/native-modules-ios) must be created that wraps the [MSAL for iOS and macOS](https://github.com/AzureAD/microsoft-authentication-library-for-objc#:~:text=The%20MSAL%20library%20for%20iOS%20and%20macOS%20gives,for%20those%20using%20our%20hosted%20identity%20management%20service.).
* B2C or ADFS
* For implementing B2C, refer to the Microsoft docs on using [MSAL for Android with B2C](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-android-b2c). Since the returned value from acquiring tokens is an [`IAuthenticationResult`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.identity.client.authenticationresult?view=azure-dotnet), the results of the wrapped B2C methods can be handled in the same manner as the existing AAD methods.
* Multiple-account mode
* This could be as simple as adding an instance of [MultipleAccountPublicClientApplication](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.multipleaccountpublicclientapplication?view=azure-java-stable) to the existing native module file and wrapping its corresponding methods.

## Methods
This library's methods wrap MSAL for Android's corresponding `singleAccountPublicClientApplication` methods. All methods return a promise that, in most cases, resolves to a map and returns an exception if rejected. Please see the Microsoft Docs for [`singleAccountPublicClientApplication`](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.singleaccountpublicclientapplication?view=azure-java-stable) for more general information regarding MSAL methods.
Method | Description |
-------------------------------------- | ----------- |
signIn(String scopesValue) | Directs a user to sign in with an AAD account. scopesValue is a String containing scopes separated by spaces (" "). Returns a map with the `authority`, `id`, `tenantId`, `username`, and `idToken` of the account. See the Microsoft docs on [IAccount](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.iaccount?view=azure-java-stable) for more details of the map's keys. |
signOut() | Signs out the account currently signed in. If successful, `true` is returned, and if not, an exception is returned. |
getAccount() | Retrieves the account currently signed in. If no account is signed in, null is returned. Otherwise, a map is returned with the `authority`, `id`, `tenantId`, `username`, and `idToken` of the account. See the Microsoft docs on [IAccount](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.iaccount?view=azure-java-stable) for more details of the map's keys. |
isSharedDevice() | Returns a boolean that corresponds to whether the current device is shared or not. |
acquireToken(String scopesValue) | Attempts to obtain an access token interactively. scopesValue is a String containing scopes separated by spaces (" "). Returns a map containing `accessToken`, `authenticationScheme`, `authorizationHeader`, `expiresOn` (string), `scope` (string), `tenantId`, and `account` (map with keys as described in "signIn"). An exception is returned otherwise. See MS docs on [IAuthenticationResult](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.iauthenticationresult?view=azure-java-stable) for more details. |
acquireTokenSilent(String scopesValue) | Attempts to obtain a token silently. scopesValue is a String containing scopes separated by spaces (" "). Returns a map containing `accessToken`, `authenticationScheme`, `authorizationHeader`, `expiresOn` (string), `scope` (string), `tenantId`, and `account` (map with keys as described in "signIn"). An exception is returned otherwise. See MS docs on [IAuthenticationResult](https://docs.microsoft.com/en-us/java/api/com.microsoft.identity.client.iauthenticationresult?view=azure-java-stable) for more details. |
6 changes: 6 additions & 0 deletions lib/msal-react-native-android-poc/android/.classpath
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
23 changes: 23 additions & 0 deletions lib/msal-react-native-android-poc/android/.project
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,2 @@
connection.project.dir=
eclipse.preferences.version=1
146 changes: 146 additions & 0 deletions lib/msal-react-native-android-poc/android/build.gradle
@@ -0,0 +1,146 @@
// android/build.gradle

// based on:
//
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle
// original location:
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle
//
// * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
// original location:
// - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle

def DEFAULT_COMPILE_SDK_VERSION = 28
def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
def DEFAULT_MIN_SDK_VERSION = 16
def DEFAULT_TARGET_SDK_VERSION = 28

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

apply plugin: 'com.android.library'
apply plugin: 'maven'

buildscript {
// The Android Gradle plugin is only required when opening the android folder stand-alone.
// This avoids unnecessary downloads and potential conflicts when the library is included as a
// module dependency in an application project.
// ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
if (project == rootProject) {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
}
}
}

apply plugin: 'com.android.library'
apply plugin: 'maven'

android {
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
versionCode 1
versionName "1.0"
}
lintOptions {
abortOnError false
}
}

repositories {
// ref: https://www.baeldung.com/maven-local-repository
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url "$rootDir/../node_modules/jsc-android/dist"
}
maven {
url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed%40Local/maven/v1'
}
google()
jcenter()
}

dependencies {
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation 'com.microsoft.identity.client:msal:1.5.+'
}

def configureReactNativePom(def pom) {
def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)

pom.project {
name packageJson.title
artifactId packageJson.name
version = packageJson.version
group = "com.reactlibrary"
description packageJson.description
url packageJson.repository.baseUrl

licenses {
license {
name packageJson.license
url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename
distribution 'repo'
}
}
}
}

afterEvaluate { project ->
// some Gradle build hooks ref:
// https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html
task androidJavadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += files(android.bootClasspath)
classpath += files(project.getConfigurations().getByName('compile').asList())
include '**/*.java'
}

task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
classifier = 'javadoc'
from androidJavadoc.destinationDir
}

task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
include '**/*.java'
}

android.libraryVariants.all { variant ->
def name = variant.name.capitalize()
def javaCompileTask = variant.javaCompileProvider.get()

task "jar${name}"(type: Jar, dependsOn: javaCompileTask) {
from javaCompileTask.destinationDir
}
}

artifacts {
archives androidSourcesJar
archives androidJavadocJar
}

task installArchives(type: Upload) {
configuration = configurations.archives
repositories.mavenDeployer {
// Deploy to react-native-event-bridge/maven, ready to publish to npm
repository url: "file://${projectDir}/../android/maven"
configureReactNativePom pom
}
}
}
@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactlibrary">

</manifest>
@@ -0,0 +1,24 @@
//Constants class which will be imported by MSALModule
package com.reactlibrary;

public final class Constants {

private Constants() {}

public static final String[] DEFAULT_SCOPES = {"User.Read"};

public static final String MODULE_NAME = "MSAL";

public static final String KEY_ACCOUNT = "account";
public static final String KEY_ACCESS_TOKEN = "accessToken";
public static final String KEY_AUTHENTICATION_SCHEME = "authenticationScheme";
public static final String KEY_AUTHORIZATION_HEADER = "authorizationHeader";
public static final String KEY_EXPIRES_ON = "expiresOn";
public static final String KEY_SCOPE = "scope";
public static final String KEY_TENANT_ID = "tenantId";

public static final String KEY_AUTHORITY = "authority";
public static final String KEY_ID = "id";
public static final String KEY_USERNAME = "username";
public static final String KEY_ID_TOKEN = "idToken";
}

0 comments on commit d77aafd

Please sign in to comment.