botframework-connector
Version:
Bot Connector is autorest generated connector client.
139 lines (124 loc) • 6.33 kB
text/typescript
/**
* @module botframework-connector
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import { VerifyOptions } from 'jsonwebtoken';
import { AuthenticationConstants } from './authenticationConstants';
import { AuthenticationConfiguration } from './authenticationConfiguration';
import { ClaimsIdentity } from './claimsIdentity';
import { ICredentialProvider } from './credentialProvider';
import { JwtTokenExtractor } from './jwtTokenExtractor';
import { AuthenticationError } from './authenticationError';
import { StatusCodes } from 'botframework-schema';
/**
* @deprecated Use `ConfigurationBotFrameworkAuthentication` instead to perform channel validation.
*/
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace ChannelValidation {
export let OpenIdMetadataEndpoint: string;
/**
* TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
*/
export const ToBotFromChannelTokenValidationParameters: VerifyOptions = {
issuer: [AuthenticationConstants.ToBotFromChannelTokenIssuer],
audience: undefined, // Audience validation takes place manually in code.
clockTolerance: 5 * 60,
ignoreExpiration: false,
};
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
* A token issued by the Bot Framework emulator will FAIL this check.
*
* @param {string} authHeader The raw HTTP header in the format: "Bearer [longString]"
* @param {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId.
* @param {string} serviceUrl The ServiceUrl Claim value that must match in the identity.
* @param {string} channelId The ID of the channel to validate.
* @param {AuthenticationConfiguration} authConfig The authentication configuration.
* @returns {Promise<ClaimsIdentity>} A valid ClaimsIdentity.
*/
export async function authenticateChannelTokenWithServiceUrl(
authHeader: string,
credentials: ICredentialProvider,
serviceUrl: string,
channelId: string,
authConfig: AuthenticationConfiguration = new AuthenticationConfiguration(),
): Promise<ClaimsIdentity> {
const identity: ClaimsIdentity = await authenticateChannelToken(authHeader, credentials, channelId, authConfig);
const serviceUrlClaim: string = identity.getClaimValue(AuthenticationConstants.ServiceUrlClaim);
if (serviceUrlClaim !== serviceUrl) {
// Claim must match. Not Authorized.
throw new AuthenticationError('Unauthorized. ServiceUrl claim do not match.', StatusCodes.UNAUTHORIZED);
}
return identity;
}
/**
* Validate the incoming Auth Header as a token sent from the Bot Framework Service.
* A token issued by the Bot Framework emulator will FAIL this check.
*
* @param {string} authHeader The raw HTTP header in the format: "Bearer [longString]"
* @param {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId.
* @param {string} channelId The ID of the channel to validate.
* @param {AuthenticationConfiguration} authConfig The authentication configuration.
* @returns {Promise<ClaimsIdentity>} A valid ClaimsIdentity.
*/
export async function authenticateChannelToken(
authHeader: string,
credentials: ICredentialProvider,
channelId: string,
authConfig: AuthenticationConfiguration = new AuthenticationConfiguration(),
): Promise<ClaimsIdentity> {
const tokenExtractor: JwtTokenExtractor = new JwtTokenExtractor(
ToBotFromChannelTokenValidationParameters,
OpenIdMetadataEndpoint ? OpenIdMetadataEndpoint : AuthenticationConstants.ToBotFromChannelOpenIdMetadataUrl,
AuthenticationConstants.AllowedSigningAlgorithms,
);
const identity: ClaimsIdentity = await tokenExtractor.getIdentityFromAuthHeader(
authHeader,
channelId,
authConfig.requiredEndorsements,
);
return await validateIdentity(identity, credentials);
}
/**
* Validate the ClaimsIdentity to ensure it came from the channel service.
*
* @param {ClaimsIdentity} identity The identity to validate
* @param {ICredentialProvider} credentials The user defined set of valid credentials, such as the AppId.
* @returns {Promise<ClaimsIdentity>} A valid ClaimsIdentity.
*/
export async function validateIdentity(
identity: ClaimsIdentity,
credentials: ICredentialProvider,
): Promise<ClaimsIdentity> {
if (!identity || !identity.isAuthenticated) {
// The token is in some way invalid. Not Authorized.
throw new AuthenticationError('Unauthorized. Is not authenticated', StatusCodes.UNAUTHORIZED);
}
// Now check that the AppID in the claimset matches
// what we're looking for. Note that in a multi-tenant bot, this value
// comes from developer code that may be reaching out to a service, hence the
// Async validation.
// Look for the "aud" claim, but only if issued from the Bot Framework
if (
identity.getClaimValue(AuthenticationConstants.IssuerClaim) !==
AuthenticationConstants.ToBotFromChannelTokenIssuer
) {
// The relevant Audiance Claim MUST be present. Not Authorized.
throw new AuthenticationError('Unauthorized. Issuer Claim MUST be present.', StatusCodes.UNAUTHORIZED);
}
// The AppId from the claim in the token must match the AppId specified by the developer.
// In this case, the token is destined for the app, so we find the app ID in the audience claim.
const audClaim: string = identity.getClaimValue(AuthenticationConstants.AudienceClaim);
if (!(await credentials.isValidAppId(audClaim || ''))) {
// The AppId is not valid or not present. Not Authorized.
throw new AuthenticationError(
`Unauthorized. Invalid AppId passed on token: ${audClaim}`,
StatusCodes.UNAUTHORIZED,
);
}
return identity;
}
}