Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth cleanup #154

Merged
merged 3 commits into from
Feb 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ namespace Microsoft.Bot.Connector.Authentication
/// </summary>
public static class AuthenticationConstants
{

public const string BotFrameworkTokenIssuer = "https://api.botframework.com";

/// <summary>
/// TO CHANNEL FROM BOT: Login URL
/// </summary>
Expand All @@ -21,6 +18,11 @@ public static class AuthenticationConstants
/// </summary>
public const string ToChannelFromBotOAuthScope = "https://api.botframework.com/.default";

/// <summary>
/// TO BOT FROM CHANNEL: Token issuer
/// </summary>
public const string ToBotFromChannelTokenIssuer = "https://api.botframework.com";

/// <summary>
/// TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA
/// </summary>
Expand All @@ -46,24 +48,24 @@ public static class AuthenticationConstants
public const string AuthorizedParty = "azp";

/// <summary>
/// Audiance Claim. From RFC 7519.
/// Audience Claim. From RFC 7519.
/// https://tools.ietf.org/html/rfc7519#section-4.1.3
/// The "aud" (audience) claim identifies the recipients that the JWT is
/// intended for. Each principal intended to process the JWT MUST
/// identify itself with a value in the audience claim.If the principal
/// intended for. Each principal intended to process the JWT MUST
/// identify itself with a value in the audience claim. If the principal
/// processing the claim does not identify itself with a value in the
/// "aud" claim when this claim is present, then the JWT MUST be
/// rejected.In the general case, the "aud" value is an array of case-
/// sensitive strings, each containing a StringOrURI value.In the
/// rejected. In the general case, the "aud" value is an array of case-
/// sensitive strings, each containing a StringOrURI value. In the
/// special case when the JWT has one audience, the "aud" value MAY be a
/// single case-sensitive string containing a StringOrURI value.The
/// single case-sensitive string containing a StringOrURI value. The
/// interpretation of audience values is generally application specific.
/// Use of this claim is OPTIONAL.
/// </summary>
public const string AudienceClaim = "aud";

/// <summary>
/// From RFC 7517
/// From RFC 7515
/// https://tools.ietf.org/html/rfc7515#section-4.1.4
/// The "kid" (key ID) Header Parameter is a hint indicating which key
/// was used to secure the JWS. This parameter allows originators to
Expand All @@ -73,6 +75,21 @@ public static class AuthenticationConstants
/// When used with a JWK, the "kid" value is used to match a JWK "kid"
/// parameter value.
/// </summary>
public const string KeyIdHeader = "kid";
public const string KeyIdHeader = "kid";

/// <summary>
/// Token version claim name. As used in Microsoft AAD tokens.
/// </summary>
public const string VersionClaim = "ver";

/// <summary>
/// App ID claim name. As used in Microsoft AAD 1.0 tokens.
/// </summary>
public const string AppIdClaim = "appid";

/// <summary>
/// Service URL claim name. As used in Microsoft Bot Framework v3.1 auth.
/// </summary>
public const string ServiceUrlClaim = "serviceurl";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@ namespace Microsoft.Bot.Connector.Authentication
{
public static class ChannelValidation
{
// This claim is ONLY used in the Channel Validation, and not in the emulator validation
private const string ServiceUrlClaim = "serviceurl";

/// <summary>
/// TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
/// </summary>
public static readonly TokenValidationParameters ToBotFromChannelTokenValidationParameters =
new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuers = new[] { AuthenticationConstants.BotFrameworkTokenIssuer },
ValidIssuers = new[] { AuthenticationConstants.ToBotFromChannelTokenIssuer },
// Audience validation takes place in JwtTokenExtractor
ValidateAudience = false,
ValidateLifetime = true,
Expand All @@ -34,15 +31,15 @@ public static class ChannelValidation
/// Validate the incoming Auth Header as a token sent from the Bot Framework Service.
/// </summary>
/// <remarks>
/// A token issued by the Bot Framework emulator will FAIL this check.
/// A token issued by the Bot Framework emulator will FAIL this check.
/// </remarks>
/// <param name="authHeader">The raw HTTP header in the format: "Bearer [longString]"</param>
/// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param>
/// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param>
/// <param name="httpClient">Authentication of tokens requires calling out to validate Endorsements and related documents. The
/// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to
/// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to
/// setup and teardown, so a shared HttpClient is recommended.</param>
/// <returns>
/// A valid ClaimsIdentity.
/// A valid ClaimsIdentity.
/// </returns>
public static async Task<ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, HttpClient httpClient)
{
Expand Down Expand Up @@ -70,18 +67,18 @@ public static async Task<ClaimsIdentity> AuthenticateChannelToken(string authHea
// Async validation.

// Look for the "aud" claim, but only if issued from the Bot Framework
Claim audianceClaim = identity.Claims.FirstOrDefault(
c => c.Issuer == AuthenticationConstants.BotFrameworkTokenIssuer && c.Type == AuthenticationConstants.AudienceClaim);
Claim audienceClaim = identity.Claims.FirstOrDefault(
c => c.Issuer == AuthenticationConstants.ToBotFromChannelTokenIssuer && c.Type == AuthenticationConstants.AudienceClaim);

if (audianceClaim == null)
if (audienceClaim == null)
{
// The relevant Audiance Claim MUST be present. Not Authorized.
// The relevant audience Claim MUST be present. Not Authorized.
throw new UnauthorizedAccessException();
}

// The AppId from the claim in the token must match the AppId specified by the developer. Note that
// the Bot Framwork uses the Audiance claim ("aud") to pass the AppID.
string appIdFromClaim = audianceClaim.Value;
// 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.
string appIdFromClaim = audienceClaim.Value;
if (string.IsNullOrWhiteSpace(appIdFromClaim))
{
// Claim is present, but doesn't have a value. Not Authorized.
Expand All @@ -96,21 +93,21 @@ public static async Task<ClaimsIdentity> AuthenticateChannelToken(string authHea

return identity;
}
/// <summary>
/// <summary>
/// Validate the incoming Auth Header as a token sent from the Bot Framework Service.
/// </summary>
/// <param name="authHeader">The raw HTTP header in the format: "Bearer [longString]"</param>
/// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param>
/// <param name="credentials">The user defined set of valid credentials, such as the AppId.</param>
/// <param name="serviceUrl"></param>
/// <param name="httpClient">Authentication of tokens requires calling out to validate Endorsements and related documents. The
/// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to
/// HttpClient is used for making those calls. Those calls generally require TLS connections, which are expensive to
/// setup and teardown, so a shared HttpClient is recommended.</param>
/// <returns></returns>
public static async Task<ClaimsIdentity> AuthenticateChannelToken(string authHeader, ICredentialProvider credentials, string serviceUrl, HttpClient httpClient)
{
var identity = await AuthenticateChannelToken(authHeader, credentials, httpClient);

var serviceUrlClaim = identity.Claims.FirstOrDefault(claim => claim.Type == ServiceUrlClaim)?.Value;
var serviceUrlClaim = identity.Claims.FirstOrDefault(claim => claim.Type == AuthenticationConstants.ServiceUrlClaim)?.Value;
if (string.IsNullOrWhiteSpace(serviceUrlClaim))
{
// Claim must be present. Not Authorized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ namespace Microsoft.Bot.Connector.Authentication
/// </summary>
public static class EmulatorValidation
{
// The AppId Claim is only used during emulator token validation.
private const string AppIdClaim = "appid";
private const string VersionClaim = "ver";

/// <summary>
/// TO BOT FROM EMULATOR: Token validation parameters when connecting to a channel.
/// </summary>
Expand Down Expand Up @@ -133,7 +129,7 @@ public static async Task<ClaimsIdentity> AuthenticateEmulatorToken(string authHe
// 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.
Claim versionClaim = identity.Claims.FirstOrDefault(c => c.Type == VersionClaim);
Claim versionClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.VersionClaim);
if (versionClaim == null)
{
throw new UnauthorizedAccessException("'ver' claim is required on Emulator Tokens.");
Expand All @@ -148,7 +144,7 @@ public static async Task<ClaimsIdentity> AuthenticateEmulatorToken(string authHe
{
// either no Version or a version of "1.0" means we should look for
// the claim in the "appid" claim.
Claim appIdClaim = identity.Claims.FirstOrDefault(c => c.Type == AppIdClaim);
Claim appIdClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AppIdClaim);
if (appIdClaim == null)
{
// No claim around AppID. Not Authorized.
Expand All @@ -169,23 +165,6 @@ public static async Task<ClaimsIdentity> AuthenticateEmulatorToken(string authHe

appID = appZClaim.Value;
}
else if (tokenVersion == "3.0")
{
// The v3.0 Token types have been disallowed. Not Authorized.
throw new UnauthorizedAccessException("Emulator token version '3.0' is depricated.");
}
else if (tokenVersion == "3.1" || tokenVersion == "3.2")
{
// The emulator for token versions "3.1" & "3.2" puts the AppId in the "Audiance" claim.
Claim audianceClaim = identity.Claims.FirstOrDefault(c => c.Type == AuthenticationConstants.AudienceClaim);
if (audianceClaim == null)
{
// No claim around AppID. Not Authorized.
throw new UnauthorizedAccessException("'aud' claim is required on Emulator Token version '3.x'.");
}

appID = audianceClaim.Value;
}
else
{
// Unknown Version. Not Authorized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ namespace Microsoft.Bot.Connector.Authentication
{
/// <summary>
/// CredentialProvider interface. This interface allows Bots to provide their own
/// implemention of what is, and what is not, a valid appId and password. This is
/// implementation of what is, and what is not, a valid appId and password. This is
/// useful in the case of multi-tenant bots, where the bot may need to call
/// out to a service to determine if a particular appid/password pair
/// is valid.
///
/// For Single Tenant bots (the vast majority) the simple static providers
/// are sufficent.
/// are sufficient.
/// </summary>
public interface ICredentialProvider
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JwtTokenExtractor
/// <summary>
/// The endorsements validator delegate.
/// </summary>
/// <param name="endorsements"> The endorsements used for validation.</param>
/// <param name="endorsements">The endorsements used for validation.</param>
/// <returns>true if validation passes; false otherwise.</returns>
public delegate bool EndorsementsValidator(string[] endorsements);

Expand Down Expand Up @@ -66,7 +66,7 @@ public class JwtTokenExtractor
/// Extracts relevant data from JWT Tokens
/// </summary>
/// <param name="httpClient">As part of validating JWT Tokens, endorsements need to be feteched from
/// sources specificed by the relevant security URLs. This HttpClient is used to allow for resource
/// sources specified by the relevant security URLs. This HttpClient is used to allow for resource
/// pooling around those retrievals. As those resources require TLS sharing the HttpClient is
/// important to overall perfomance.</param>
/// <param name="tokenValidationParameters"></param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ public static class JwtTokenValidation
/// <param name="activity">The incoming Activity from the Bot Framework or the Emulator</param>
/// <param name="authHeader">The Bearer token included as part of the request</param>
/// <param name="credentials">The set of valid credentials, such as the Bot Application ID</param>
/// <param name="httpClient">Validing an Activity requires validating the claimset on the security token. This
/// <param name="httpClient">Validating an Activity requires validating the claimset on the security token. This
/// validation may require outbound calls for Endorsement validation and other checks. Those calls are made to
/// TLS services, which are (latency wise) expensive resources. The httpClient passed in here, if shared by the layers
/// above from call to call, enables connection reuse which is a signifant performance and resource improvement.</param>
/// above from call to call, enables connection reuse which is a significant performance and resource improvement.</param>
/// <returns>Nothing</returns>
public static async Task AssertValidActivity(Activity activity, string authHeader, ICredentialProvider credentials, HttpClient httpClient)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class MicrosoftAppCredentials : ServiceClientCredentials

/// <summary>
/// The token refresh code uses this client. Ideally, this would be passed in or set via a DI system to
/// allow developer control over behavior / headers / timesouts and such. Unfortunatly this is buried
/// allow developer control over behavior / headers / timeouts and such. Unfortunately this is buried
/// pretty deep, the static solution used here is much cleaner. If this becomes an issue we could
/// consider circling back and exposing developer control over this HttpClient.
/// </summary>
Expand Down