BotAuthenticator.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Net;
5 using System.Net.Http;
6 using System.Security.Claims;
7 using System.Threading;
8 using System.Threading.Tasks;
9 #if NET45
10 using System.Diagnostics;
11 using System.Web;
12 #endif
13 
14 
15 namespace Microsoft.Bot.Connector
16 {
17 
18  public sealed class BotAuthenticator
19  {
20  private readonly ICredentialProvider credentialProvider;
21  private readonly string openIdConfigurationUrl;
22  private readonly bool disableEmulatorTokens;
23 
33  public BotAuthenticator(string microsoftAppId, string microsoftAppPassword)
34  {
35  this.credentialProvider = new StaticCredentialProvider(microsoftAppId, microsoftAppPassword);
36  this.openIdConfigurationUrl = JwtConfig.ToBotFromChannelOpenIdMetadataUrl;
37  // by default Authenticator is not disabling emulator tokens
38  this.disableEmulatorTokens = false;
39  }
40 
41  public BotAuthenticator(ICredentialProvider credentialProvider)
42  : this(credentialProvider, JwtConfig.ToBotFromChannelOpenIdMetadataUrl, false)
43  {
44  }
45 
46  public BotAuthenticator(ICredentialProvider credentialProvider, string openIdConfigurationUrl,
47  bool disableEmulatorTokens)
48  {
49  if (credentialProvider == null)
50  {
51  throw new ArgumentNullException(nameof(credentialProvider));
52  }
53  this.credentialProvider = credentialProvider;
54  this.openIdConfigurationUrl = openIdConfigurationUrl;
55  this.disableEmulatorTokens = disableEmulatorTokens;
56 
57  }
58 
67  public async Task<bool> TryAuthenticateAsync(HttpRequestMessage request, IEnumerable<IActivity> activities,
68  CancellationToken token)
69  {
70  var identityToken = await this.TryAuthenticateAsync(request, token);
71  TrustServiceUrls(identityToken, activities);
72  return identityToken.Authenticated;
73  }
74 
80  public static HttpResponseMessage GenerateUnauthorizedResponse(HttpRequestMessage request)
81  {
82  string host = request.RequestUri.DnsSafeHost;
83 #if NET45
84  var response = request.CreateResponse(HttpStatusCode.Unauthorized);
85 #else
86  var response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
87 #endif
88  response.Headers.Add("WWW-Authenticate", string.Format("Bearer realm=\"{0}\"", host));
89  return response;
90  }
91 
92  internal void TrustServiceUrls(IdentityToken identityToken, IEnumerable<IActivity> activities)
93  {
94  // add the service url to the list of trusted urls only if the JwtToken
95  // is valid and identity is not null
96  if (identityToken.Authenticated && identityToken.Identity != null)
97  {
98  if (activities.Any())
99  {
100  foreach (var activity in activities)
101  {
102  MicrosoftAppCredentials.TrustServiceUrl(activity?.ServiceUrl);
103  }
104  }
105  else
106  {
107 #if NET45
108  Trace.TraceWarning("No ServiceUrls added to trusted list");
109 #endif
110  }
111  }
112  }
113 
114  internal async Task<IdentityToken> TryAuthenticateAsync(HttpRequestMessage request,
115  CancellationToken token)
116  {
117  var authorizationHeader = request.Headers.Authorization;
118  if (authorizationHeader != null)
119  {
120  return await TryAuthenticateAsync(authorizationHeader.Scheme, authorizationHeader.Parameter, token);
121  }
122  else if (await this.credentialProvider.IsAuthenticationDisabledAsync())
123  {
124  return new IdentityToken(true, null);
125  }
126 
127  return new IdentityToken(false, null);
128  }
129 
130  public async Task<IdentityToken> TryAuthenticateAsync(string scheme, string token,
131  CancellationToken cancellationToken)
132  {
133  // then auth is disabled
134  if (await this.credentialProvider.IsAuthenticationDisabledAsync())
135  {
136  return new IdentityToken(true, null);
137  }
138 
139  ClaimsIdentity identity = null;
140  string appId = null;
141  var tokenExtractor = GetTokenExtractor();
142  identity = await tokenExtractor.GetIdentityAsync(scheme, token);
143  if (identity != null)
144  appId = tokenExtractor.GetAppIdFromClaimsIdentity(identity);
145 
146  // No identity? If we're allowed to, fall back to MSA
147  // This code path is used by the emulator
148  if (identity == null && !this.disableEmulatorTokens)
149  {
151  identity = await tokenExtractor.GetIdentityAsync(scheme, token);
152 
153  if (identity != null)
154  appId = tokenExtractor.GetAppIdFromEmulatorClaimsIdentity(identity);
155  }
156 
157  if (identity != null)
158  {
159  if (await credentialProvider.IsValidAppIdAsync(appId) == false) // keep context
160  {
161  // not valid appid, drop the identity
162  identity = null;
163  }
164  else
165  {
166  var password = await credentialProvider.GetAppPasswordAsync(appId); // Keep context
167  if (password != null)
168  {
169  // add password as claim so that it is part of ClaimsIdentity and accessible by ConnectorClient()
170  identity.AddClaim(new Claim(ClaimsIdentityEx.AppPasswordClaim, password));
171  }
172  }
173  }
174 
175  if (identity != null)
176  {
177 #if NET45
178  Thread.CurrentPrincipal = new ClaimsPrincipal(identity);
179 
180  // Inside of ASP.NET this is required
181  if (HttpContext.Current != null)
182  HttpContext.Current.User = Thread.CurrentPrincipal;
183 #endif
184 
185  return new IdentityToken(true, identity);
186  }
187 
188  return new IdentityToken(false, null);
189  }
190 
191  private JwtTokenExtractor GetTokenExtractor()
192  {
194  return new JwtTokenExtractor(parameters, this.openIdConfigurationUrl);
195  }
196 
197  }
198 
199  public sealed class IdentityToken
200  {
201  public readonly bool Authenticated;
202  public readonly ClaimsIdentity Identity;
203 
204  public IdentityToken(bool authenticated, ClaimsIdentity identity)
205  {
206  this.Authenticated = authenticated;
207  this.Identity = identity;
208  }
209  }
210 }
BotAuthenticator(ICredentialProvider credentialProvider, string openIdConfigurationUrl, bool disableEmulatorTokens)
async Task< bool > TryAuthenticateAsync(HttpRequestMessage request, IEnumerable< IActivity > activities, CancellationToken token)
Authenticates the incoming request and add the IActivity.ServiceUrl for each activities to MicrosoftA...
IdentityToken(bool authenticated, ClaimsIdentity identity)
static readonly TokenValidationParameters ToBotFromEmulatorTokenValidationParameters
TO BOT FROM EMULATOR: Token validation parameters when connecting to a channel
Definition: JwtConfig.cs:53
BotAuthenticator(ICredentialProvider credentialProvider)
const string ToBotFromChannelOpenIdMetadataUrl
TO BOT FROM CHANNEL: OpenID metadata document for tokens coming from MSA
Definition: JwtConfig.cs:28
static readonly TokenValidationParameters ToBotFromChannelTokenValidationParameters
TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot
Definition: JwtConfig.cs:33
async Task< IdentityToken > TryAuthenticateAsync(string scheme, string token, CancellationToken cancellationToken)
static void TrustServiceUrl(string serviceUrl, DateTime expirationTime=default(DateTime))
Adds the host of service url to MicrosoftAppCredentials trusted hosts.
Configuration for JWT tokens
Definition: JwtConfig.cs:13
static HttpResponseMessage GenerateUnauthorizedResponse(HttpRequestMessage request)
Generates HttpStatusCode.Unauthorized response for the request.
Static credential provider which has the appid and password static
const string ToBotFromEmulatorOpenIdMetadataUrl
TO BOT FROM EMULATOR: OpenID metadata document for tokens coming from MSA
Definition: JwtConfig.cs:48
BotAuthenticator(string microsoftAppId, string microsoftAppPassword)
Creates an instance of bot authenticator.