@microsoft/teams.apps
Version:
<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code
222 lines • 19.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEntraTokenValidator = exports.JwtValidator = void 0;
exports.decodeJwtPayload = decodeJwtPayload;
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
const teams_common_1 = require("@microsoft/teams.common");
const asserts_1 = require("../../utils/asserts");
const DEFAULTS = {
clockTolerance: 300 // 5 minutes
};
function decodeJwtPayload(rawToken) {
const payload = jsonwebtoken_1.default.decode(rawToken);
if (!payload || typeof payload !== 'object') {
return null;
}
return payload;
}
class JwtValidator {
options;
logger;
jwksCache = new Map();
constructor(options, logger) {
this.options = options;
this.logger = logger?.child('jwt-validator') ?? new teams_common_1.ConsoleLogger('jwt-validator');
}
/**
* Validates a JWT token using the configured options
*/
async validateAccessToken(rawToken, overrideOptions) {
if (!rawToken) {
throw new Error('No token provided');
}
return new Promise((resolve) => {
const verifyOptions = {
audience: [
this.options.clientId,
`api://botid-${this.options.clientId}`,
`api://${this.options.clientId}`,
...(this.options.audience ?? []),
],
issuer: undefined,
ignoreExpiration: false,
algorithms: ['RS256'],
clockTolerance: this.options.clockTolerance ?? DEFAULTS.clockTolerance
};
this.logger?.debug('Validating JWT token with options:', {
audience: verifyOptions.audience,
clockTolerance: verifyOptions.clockTolerance,
algorithms: verifyOptions.algorithms
});
jsonwebtoken_1.default.verify(rawToken, this.getSigningKey.bind(this), verifyOptions, (err, decoded) => {
if (err) {
this.logger?.error('JWT verification failed:', err);
resolve(null);
return;
}
if (!decoded || typeof decoded !== 'object') {
this.logger?.error('Decoded token is not a valid object:', decoded);
resolve(null);
return;
}
this.logger?.debug('JWT verification succeeded');
const payload = decoded;
try {
this.performCustomValidations(payload, overrideOptions);
this.logger?.debug('Custom validations passed for token');
resolve(payload);
}
catch (validationError) {
this.logger?.error('Custom validation failed:', validationError);
resolve(null);
}
});
});
}
getJwksClient() {
switch (this.options.jwksUriOptions.type) {
case 'tenantId':
{
const cachedClient = this.jwksCache.get(`${this.options.tenantId}`);
if (cachedClient) {
this.logger?.debug(`Using cached JWKS client for tenant ID: ${this.options.tenantId}`);
return cachedClient;
}
const loginEndpoint = this.options.loginEndpoint ?? 'https://login.microsoftonline.com';
this.jwksCache.set(`${this.options.tenantId}`, (0, jwks_rsa_1.default)({
jwksUri: `${loginEndpoint}/${this.options.tenantId}/discovery/v2.0/keys`,
}));
return this.jwksCache.get(`${this.options.tenantId}`);
}
case 'uri':
{
const cachedClient = this.jwksCache.get(this.options.jwksUriOptions.uri);
if (cachedClient) {
this.logger?.debug(`Using cached JWKS client for URI: ${this.options.jwksUriOptions.uri}`);
return cachedClient;
}
this.jwksCache.set(this.options.jwksUriOptions.uri, (0, jwks_rsa_1.default)({
jwksUri: this.options.jwksUriOptions.uri,
}));
return this.jwksCache.get(this.options.jwksUriOptions.uri);
}
default:
(0, asserts_1.assertNever)(this.options.jwksUriOptions, `Unknown JWKS URI options type: ${this.options.jwksUriOptions}`);
}
}
getSigningKey(header, callback) {
const jwksClient = this.getJwksClient();
jwksClient?.getSigningKey(header.kid, (err, key) => {
if (err) {
this.logger?.error('Failed to get signing key:', err);
callback(err, undefined);
return;
}
const signingKey = key?.getPublicKey();
callback(null, signingKey);
});
}
validateIssuer(iss) {
const validateIssuer = this.options.validateIssuer;
if (!validateIssuer) {
return;
}
// Check for allowedIssuer (exact match validation)
if ('allowedIssuer' in validateIssuer) {
if (!validateIssuer.allowedIssuer) {
return;
}
if (!iss) {
throw new Error('Token missing issuer claim');
}
if (iss !== validateIssuer.allowedIssuer) {
throw new Error(`Token issuer '${iss}' does not match allowed issuer '${validateIssuer.allowedIssuer}'`);
}
}
// Check for allowedTenantIds (tenant-based validation)
if ('allowedTenantIds' in validateIssuer) {
if (!validateIssuer.allowedTenantIds?.length) {
return;
}
if (!iss) {
throw new Error('Token missing issuer claim');
}
if (!this.options.tenantId) {
throw new Error('Tenant ID is required when allowedTenantIds is configured');
}
const isMultiTenant = ['common', 'organizations', 'consumers'].includes(this.options.tenantId);
const allowedTenantIds = [];
if (isMultiTenant) {
for (const tenantId of validateIssuer.allowedTenantIds) {
if (!['common', 'organizations', 'consumers'].includes(tenantId)) {
allowedTenantIds.push(tenantId);
}
}
}
else {
// For single-tenant apps, only allow tokens issued by this app's tenant
allowedTenantIds.push(this.options.tenantId);
}
if (allowedTenantIds.length === 0) {
return;
}
const issuerEndpoints = [
this.options.loginEndpoint ?? 'https://login.microsoftonline.com',
'https://sts.windows.net',
];
if (!allowedTenantIds.some((tenantId) => issuerEndpoints.some((endpoint) => iss.startsWith(`${endpoint}/${tenantId}/`)))) {
throw new Error(`Token issuer '${iss}' not in allowed tenant IDs: ${allowedTenantIds.join(', ')}`);
}
}
}
validateScope(scp, overrideValidateScope) {
const validateScope = overrideValidateScope || this.options.validateScope;
if (validateScope) {
const scopeSet = new Set((scp ?? '').split(' '));
if (!scopeSet.has(validateScope.requiredScope)) {
throw new Error(`Token missing required scope: ${validateScope.requiredScope}`);
}
}
}
validateServiceUrl(serviceUrl, overrideValidateServiceUrl) {
const validateServiceUrl = overrideValidateServiceUrl || this.options.validateServiceUrl;
if (validateServiceUrl) {
if (!serviceUrl) {
throw new Error('Token missing serviceurl claim');
}
const normalizedTokenUrl = serviceUrl.replace(/\/$/, '').toLowerCase();
const normalizedExpectedUrl = validateServiceUrl.expectedServiceUrl.replace(/\/$/, '').toLowerCase();
if (normalizedTokenUrl !== normalizedExpectedUrl) {
throw new Error(`Service URL mismatch. Token: ${normalizedTokenUrl}, Expected: ${normalizedExpectedUrl}`);
}
}
}
performCustomValidations(payload, overrideOptions) {
this.validateIssuer(payload.iss);
this.validateScope(payload.scp, overrideOptions?.validateScope);
this.validateServiceUrl(payload.serviceurl, overrideOptions?.validateServiceUrl);
}
}
exports.JwtValidator = JwtValidator;
// Factory functions for common scenarios
const createEntraTokenValidator = (tenantId, clientId, options) => {
return new JwtValidator({
clientId,
tenantId,
loginEndpoint: options?.loginEndpoint,
audience: options?.applicationIdUri ? [options.applicationIdUri] : undefined,
validateIssuer: {
allowedTenantIds: options?.allowedTenantIds
},
validateScope: options?.requiredScope ? { requiredScope: options.requiredScope } : undefined,
jwksUriOptions: {
type: 'tenantId'
},
}, options?.logger);
};
exports.createEntraTokenValidator = createEntraTokenValidator;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiand0LXZhbGlkYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9taWRkbGV3YXJlL2F1dGgvand0LXZhbGlkYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFXQSw0Q0FPQztBQWxCRCxnRUFBdUY7QUFDdkYsd0RBQTJEO0FBRTNELDBEQUFpRTtBQUVqRSxpREFBa0Q7QUFFbEQsTUFBTSxRQUFRLEdBQUc7SUFDZixjQUFjLEVBQUUsR0FBRyxDQUFDLFlBQVk7Q0FDakMsQ0FBQztBQUVGLFNBQWdCLGdCQUFnQixDQUFDLFFBQWdCO0lBQy9DLE1BQU0sT0FBTyxHQUFHLHNCQUFHLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ3JDLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDNUMsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0lBRUQsT0FBTyxPQUFPLENBQUM7QUFDakIsQ0FBQztBQW1ERCxNQUFhLFlBQVk7SUFDUCxPQUFPLENBQXdCO0lBQzlCLE1BQU0sQ0FBVztJQUNqQixTQUFTLEdBQTRCLElBQUksR0FBRyxFQUFFLENBQUM7SUFFaEUsWUFBWSxPQUE4QixFQUFFLE1BQWdCO1FBQzFELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxFQUFFLEtBQUssQ0FBQyxlQUFlLENBQUMsSUFBSSxJQUFJLDRCQUFhLENBQUMsZUFBZSxDQUFDLENBQUM7SUFDckYsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLG1CQUFtQixDQUN2QixRQUFnQixFQUNoQixlQUFxRjtRQUVyRixJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLG1CQUFtQixDQUFDLENBQUM7UUFDdkMsQ0FBQztRQUVELE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFP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