botbuilder-simple-authentication-custom
TypeScript icon, indicating that this package has built-in type declarations

1.0.3 • Public • Published

botbuilder-simple-authentication

botbuilder-simple-authentication exposes middleware designed to make authentication implementation quick and easy for Bot Framework Adapters.

Table of Contents

  1. Basic Usage
  2. Samples
  3. Configuration Properties
  4. Usage with Express
  5. Using Environment Variables
  6. Custom Scopes
  7. Custom Button Text
  8. Custom Authentication Card
  9. Custom Magic Code HTML
  10. Custom Azure AD Tenant and Resource

Basic Usage

BotAuthenticationMiddleware assumes control of the conversation flow when a user is not authenticated and provides the user's access token and profile after successful login.

The middleware minimally requires 3 configuration properties:

  1. A method that returns whether the user is authenticated or not.
  2. A method that is triggered after the user has logged in successfully that receives the user's access token and profile.
  3. At least one clientId/clientSecret for an application created with a supported provider.

Installation

npm install botbuilder-simple-authentication

Import the botbuilder-simple-authentication module

let simpleAuth = require('botbuilder-simple-authentication');

Create a BotAuthenticationConfiguration

The following example uses in memory storage, but any storage service can be used to store and manage authentication data. BotAuthenticationConfiguration methods can be synchronous or asynchronous.

let storage = new builder.MemoryStorage();
const conversationState = new builder.ConversationState(storage);
adapter.use(conversationState);

const authenticationConfig = {
	isUserAuthenticated: (context) => {
		//if this method returns false, the middleware will take over
		const state = conversationState.get(context);
		return state.authData;
	},
	onLoginSuccess: async (context, accessToken, profile, provider) => {
		//the middleware passes over the access token and profile retrieved for the user
		const state = conversationState.get(context);
		state.authData = { accessToken, profile, provider };
		await context.sendActivity(`Hi there ${profile.displayName}!`);
	},
	facebook: {
		clientId: 'FACEBOOK_CLIENT_ID',
		clientSecret: 'FACEBOOK_CLIENT_SECRET'
	},
	azureADv1: {
		clientId: 'AZURE_AD_V1_CLIENT_ID',
		clientSecret: 'AZURE_AD_V1_CLIENT_SECRET'
	},
	azureADv2: {
		clientId: 'AZURE_AD_V2_CLIENT_ID',
		clientSecret: 'AZURE_AD_V2_CLIENT_SECRET'
	},
	google: {
		clientId: 'GOOGLE_CLIENT_ID',
		clientSecret: 'GOOGLE_CLIENT_SECRET'
	},
	twitter: {
		consumerKey: 'TWITTER_CONSUMER_KEY',
		consumerSecret: 'TWITTER_CONSUMER_SECRET'
	},
	github: {
		clientId: 'GITHUB_CLIENT_ID',
		clientSecret: 'GITHUB_CLIENT_SECRET'
	}
};

Implement the BotAuthenticationMiddleware

Create a new instance of the middleware, passing in the BotAuthenticationConfiguration along with your server (compatible with Restify or Express).

adapter.use(new simpleAuth.BotAuthenticationMiddleware(server, authenticationConfig));

Create an Application with a Supported Provider

Navigate to a supported provider's developer site listed below and create a new application. Add the appropriate Redirect URL to your app's approved redirect urls, then copy the clientId and clientSecret used to create the BotAuthenticationConfiguration.

Supported Providers Redirect URL Developer Site
Facebook {BASE_URL}/auth/facebook/callback https://developers.facebook.com/apps
AzureADv1 {BASE_URL}/auth/azureAD/callback https://apps.dev.microsoft.com
AzureADv2 {BASE_URL}/auth/azureAD/callback https://apps.dev.microsoft.com
Google {BASE_URL}/auth/google/callback https://console.cloud.google.com/home
Twitter {BASE_URL}/auth/twitter/callback https://apps.twitter.com
GitHub {BASE_URL}/auth/github/callback https://github.com/settings/developers

Samples

The samples folder contains basic examples with minimal configuration and advanced examples that implement each of the optional configuration properties, for both JavaScript and TypeScript.

Configuration Properties

BotAuthenticationConfiguration

Property Constraint Type Description
isUserAuthenticated Required (context: TurnContext) => boolean Runs each converation turn. The middleware will prevent the bot logic from running when it returns false.
onLoginSuccess Required (context: TurnContext, accessToken: string, profile: any, provider: string) => void Runs when the user inputs the correct magic code. The middleware passes the user's access token and profile.
onLoginFailure Optional (context: TurnContext, provider: string) => void Runs when the user inputs an incorrect magic code. The middleware will force another login attempt by default.
customAuthenticationCardGenerator Optional (context: TurnContext, authorizationUris: {}[]) => Partial< Activity > Overrides the default Authentication Card. The middleware supplies the authorization uris necessary to build the card.
customMagicCodeRedirectEndpoint Optional string Overrides the default magic code display page. The server endpoint provided will receive a redirect with the magic code in the query string.
noUserFoundMessage Optional string Message sent on first conversation turn where the user is not authenticated, immediately prior to the Authentication Card.
facebook Optional DefaultProviderConfiguration Configuration object that enables Facebook authentication.
azureADv1 Optional AzureADConfiguration Configuration object that enables AzureADv1 authentication.
azureADv2 Optional AzureADConfiguration Configuration object that enables AzureADv2 authentication.
google Optional DefaultProviderConfiguration Configuration object that enables Google authentication.
twitter Optional TwitterConfiguration Configuration object that enables Twitter authentication.
github Optional DefaultProviderConfiguration Configuration object that enables GitHub authentication.

DefaultProviderConfiguration

Property Constraint Type Description
clientId Required string Client Id taken from the provider's authentication application.
clientSecret Required string Client Secret taken from the provider's authentication application.
scopes Optional string[] Scopes that the user will be asked to consent to as part of the authentication flow.
buttonText Optional string Text displayed inside the button that triggers the provider's authentication flow.

AzureADConfiguration

Property Constraint Type Description
clientId Required string Application Id taken from the Microsoft Application Registration Portal.
clientSecret Required string Application Secret taken from the Microsoft Application Registration Portal.
scopes Optional string[] Scopes that the user will be asked to consent to as part of the authentication flow.
buttonText Optional string Text displayed inside the button that triggers the provider's authentication flow.
tenant Optional string Organizational tenant domain.
resource Optional string Identifier of the WebAPI that your client wants to access on behalf of the user

TwitterConfiguration

Property Constraint Type Description
consumerKey Required string Consumer Key taken from the Twitter Application Management page.
consumerSecret Required string Consumer Secret taken from the Twitter Application Management page.
buttonText Optional string Text displayed inside the button that triggers the provider's authentication flow.

Usage With Express

IMPORTANT - For use with Express, the middleware must be instantiated before the adapter.processActivity() statement.

Express Application

Create an Express Application

let express = require('express');
let app = express();

Implement the BotAuthenticationMiddleware

adapter.use(new simpleAuth.BotAuthenticationMiddleware(app, authenticationConfig));

Express Router

Create an Express Application

let express = require('express');
let app = express();

Create a Router

let router = express.Router();
app.use('/', router);

Implement the BotAuthenticationMiddleware

adapter.use(new simpleAuth.BotAuthenticationMiddleware(router, authenticationConfig));

Using Environment Variables

Provider clientIds and clientSecrets can be set via environment variables and do not have to be set in provider configuration objects.

BotAuthenticationConfiguration Property Environment Variable
facebook.clientId FACEBOOK_CLIENT_ID
facebook.clientSecret FACEBOOK_CLIENT_SECRET
azureADv1.clientId AZURE_AD_V1_CLIENT_ID
azureADv1.clientSecret AZURE_AD_V1_CLIENT_SECRET
azureADv2.clientId AZURE_AD_V2_CLIENT_ID
azureADv2.clientSecret AZURE_AD_V2_CLIENT_SECRET
google.clientId GOOGLE_CLIENT_ID
google.clientSecret GOOGLE_CLIENT_SECRET
twitter.consumerKey TWITTER_CONSUMER_KEY
twitter.consumerSecret TWITTER_CONSUMER_SECRET
github.clientId GITHUB_CLIENT_ID
github.clientSecret GITHUB_CLIENT_SECRET

Example .env

FACEBOOK_CLIENT_ID = {VALUE}
FACEBOOK_CLIENT_SECRET = {VALUE}
AZURE_AD_V2_CLIENT_ID = {VALUE}
AZURE_AD_V2_CLIENT_SECRET = {VALUE}

Example BotAuthenticationConfiguration

let storage = new builder.MemoryStorage();
const conversationState = new builder.ConversationState(storage);
adapter.use(conversationState);

const authenticationConfig = {
	isUserAuthenticated: (context) => {
		//if this method returns false, the middleware will take over
		const state = conversationState.get(context);
		return state.authData;
	},
	onLoginSuccess: async (context, accessToken, profile, provider) => {
		//the middleware passes over the access token and profile retrieved for the user
		const state = conversationState.get(context);
		state.authData = { accessToken, profile, provider };
		await context.sendActivity(`Hi there ${profile.displayName}!`);
	},
};

Custom Scopes

Each provider declared in the BotAuthenticationConfiguration object except for Twitter has an optional scope property that accepts an array of strings (Twitter scopes are set in the Twitter Application Management page). If custom scopes aren't provided, the following scopes are used by default:

Provider Scopes
Facebook public_profile
AzureADv1 User.Read
AzureADv2 profile
Google https://www.googleapis.com/auth/plus.login
GitHub user

Default Scopes

facebook: {
	clientId: 'FACEBOOK_CLIENT_ID',
	clientSecret: 'FACEBOOK_CLIENT_SECRET'
}

Example Custom Scopes

facebook: {
	clientId: 'FACEBOOK_CLIENT_ID',
	clientSecret: 'FACEBOOK_CLIENT_SECRET'
	scopes: ['public_profile', 'email', 'user_likes']
}

Custom Button Text

Each provider declared in the BotAuthenticationConfiguration object has an optional buttonText property that accepts a string. If custom button text isn't provided, the following strings are used by default:

Provider Button Text
Facebook Log in with Facebook
AzureADv1 Log in with Microsoft
AzureADv2 Log in with Microsoft
Google Log in with Google+
Twitter Log in with Twitter
GitHub Log in with GitHub

Default Button Text

facebook: {
	clientId: 'FACEBOOK_CLIENT_ID',
	clientSecret: 'FACEBOOK_CLIENT_SECRET'
}

Example Custom Button Text

facebook: {
	clientId: 'FACEBOOK_CLIENT_ID',
	clientSecret: 'FACEBOOK_CLIENT_SECRET'
	buttonText: 'Facebook'
}

Custom Authentication Card

The customAuthenticationCardGenerator property is used to override the default card. The method receives the authorization uris for each provider set in the BotAuthenticationConfiguration and is responsible for navigating the user to one of them.

Default Authentication Card

Example Custom Authentication Card

customAuthenticationCardGenerator: async (context, authorizationUris) => {
	let cardActions = [];
	let buttonTitle;
	authorizationUris.map((auth) => {
		if (auth.provider === 'azureADv1' || auth.provider === 'azureADv2') {
			buttonTitle = 'Microsoft';
		} else if (auth.provider === 'facebook') {
			buttonTitle = 'Facebook';
		} else if (auth.provider === 'google') {
			buttonTitle = 'Google';
		} else if (auth.provider === 'twitter') {
			buttonTitle = 'Twitter';
		} else if (auth.provider === 'github') {
			buttonTitle = 'GitHub';
		}
		cardActions.push({ type: 'openUrl', value: auth.authorizationUri, title: buttonTitle });
	});
	let card = builder.CardFactory.heroCard('', ['https://qualiscare.com/wp-content/uploads/2017/08/default-user.png'], cardActions);
	let authMessage = builder.MessageFactory.attachment(card);
	return authMessage;
}

Custom Magic Code HTML

The BotAuthenticationConfiguration object has an optional customMagicCodeRedirectEndpoint property used to override the default magic code HTML display and create a custom page.

In order to fully implement a custom page, the server passed to the middleware will need to expose an endpoint that is referenced by the customMagicCodeRedirectEndpoint property. BotAuthenticationMiddleware adds the queryParser middleware to the restify server and redirects to this endpoint with the magic code in the query string, so the code is accessible via req.query.magicCode. The server is responsible for serving an HTML page capable of displaying the magic code.

Default Magic Code HTML

Example Custom Magic Code HTML

In the example below, restify exposes an endpoint that serves up an html file expecting a magic code in the URL's hash. The customMagicCodeRedirectEndpoint property is set to another endpoint that parses the magic code and sends it in the hash to the html file.

/app.js

let storage = new builder.MemoryStorage();
const conversationState = new builder.ConversationState(storage);
adapter.use(conversationState);

const authenticationConfig = {
	isUserAuthenticated: (context) => {
		//if this method returns false, the middleware will take over
		const state = conversationState.get(context);
		return state.authData;
	},
	onLoginSuccess: async (context, accessToken, profile, provider) => {
		//the middleware passes over the access token and profile retrieved for the user
		const state = conversationState.get(context);
		state.authData = { accessToken, profile, provider };
		await context.sendActivity(`Hi there ${profile.displayName}!`);
	},
	facebook: {
		clientId: 'FACEBOOK_CLIENT_ID',
		clientSecret: 'FACEBOOK_CLIENT_SECRET'
	},
	customMagicCodeRedirectEndpoint: '/customCode'
};

server.get('/customCode', (req, res, next) => {
	//simple redirect where we set the code in the hash and pull it down on the webpage that restify will serve at this endpoint
	let magicCode = req.query.magicCode;
	let hashedUrl = `/renderCustomCode#${magicCode}`;
	res.redirect(302, hashedUrl, next);
});

server.get('/renderCustomCode', restify.plugins.serveStatic({
	//need a public folder in the same directory as this file that contains an index.html page expecting a hash
	'directory': path.join(__dirname, 'public'),
	'file': 'index.html'
}));

/public/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Authentication Bot - Login Success</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
        crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
        crossorigin="anonymous">
    <style>
        body {
            -moz-user-select: none;
            -webkit-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }
        .navbar {
            border-bottom: 3px solid #FFDB00;
            font-variant: small-caps
        }
        body {
            background-color: #EEEEEE;
        }
        .jumbotron {
            border: 3px solid #FFDB00;
            background-color: #333333;
            color: white;
        }
        h3 {
            font-variant: small-caps;
        }
        logo {
            margin-bottom: 1em;
        }
        #magic_code {
            font-size: 2em;
            font-family: monospace;
            font-weight: bold;
            -moz-user-select: all;
            -webkit-user-select: text;
            -ms-user-select: text;
            user-select: all;
        }
        .jumbotron {
            text-align: center;
        }
        .title {
            color: white !important;
        }
    </style>
</head>
<body>
    <nav class="navbar navbar-inverse navbar-static-top">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand logo">
                    <kbd><img src="http://icons.iconarchive.com/icons/paomedia/small-n-flat/256/key-icon.png" width="27" height="25" alt="">
                </a>
                <a class="navbar-brand title">Authentication Bot</a>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="jumbotron">
            <h3>Please type the magic code below into your conversation with the bot</h3>
        </div>
        <div class="jumbotron">
            <div id="magic_code"></div>
        </div>
    </div>
    <script type="text/javascript">
        document.getElementById("magic_code").innerText = (window.location.hash || '').replace('#', '');
    </script>
</body>
</html>

Custom Azure AD Tenant and Resource

The AzureADv1 and AzureADv2 providers declared in the BotAuthenticationConfiguration object have an optional tenant property that accepts a string. If a custom tenant isn't provided, the common endpoint is used by default:

Default Tenant

azureADv2: {
	clientId: 'AZURE_AD_V2_CLIENT_ID',
	clientSecret: 'AZURE_AD_V2_CLIENT_SECRET'
}

Example Custom Tenant (V1 or V2)

azureADv2: {
	clientId: 'AZURE_AD_V2_CLIENT_ID',
	clientSecret: 'AZURE_AD_V2_CLIENT_SECRET',
	tenant: 'microsoft.onmicrosoft.com'
}

The AzureADv1 provider declared in the BotAuthenticationConfiguration object has an optional resource property that accepts a string. If a custom resource isn't provided, the Microsoft Graph is used by default:

Default Resource

azureADv1: {
	clientId: 'AZURE_AD_V1_CLIENT_ID',
	clientSecret: 'AZURE_AD_V1_CLIENT_SECRET'
}

Example Custom Resource (V1 Only)

azureADv1: {
	clientId: 'AZURE_AD_V1_CLIENT_ID',
	clientSecret: 'AZURE_AD_V1_CLIENT_SECRET',
	//VSTS API resource
	resource: '499b84ac-1321-427f-aa17-267ca6975798'
}

Package Sidebar

Install

npm i botbuilder-simple-authentication-custom

Weekly Downloads

7

Version

1.0.3

License

ISC

Unpacked Size

98.3 kB

Total Files

17

Last publish

Collaborators

  • yudai.tsuji