JwtTokenExtractor.cs
1 using System;
2 using System.Collections.Generic;
3 #if NET45
4 using System.Diagnostics;
5 #endif
6 #if NET45
7 using System.IdentityModel.Tokens;
8 #else
9 using System.IdentityModel.Tokens.Jwt;
10 #endif
11 using System.Linq;
12 using System.Net.Http;
13 using System.Security.Claims;
14 using System.Threading.Tasks;
15 
16 using Microsoft.IdentityModel.Protocols;
17 #if !NET45
18 using Microsoft.IdentityModel.Protocols.OpenIdConnect;
19 using Microsoft.IdentityModel.Tokens;
20 #endif
21 
22 namespace Microsoft.Bot.Connector
23 {
24  public class JwtTokenExtractor
25  {
29  private static readonly Dictionary<string, ConfigurationManager<OpenIdConnectConfiguration>> _openIdMetadataCache =
30  new Dictionary<string, ConfigurationManager<OpenIdConnectConfiguration>>();
31 
35  private readonly TokenValidationParameters _tokenValidationParameters;
36 
40  private readonly ConfigurationManager<OpenIdConnectConfiguration> _openIdMetadata;
41 
42  public JwtTokenExtractor(TokenValidationParameters tokenValidationParameters, string metadataUrl)
43  {
44  // Make our own copy so we can edit it
45  _tokenValidationParameters = tokenValidationParameters.Clone();
46  _tokenValidationParameters.RequireSignedTokens = true;
47 
48  if (!_openIdMetadataCache.ContainsKey(metadataUrl))
49 #if NET45
50  _openIdMetadataCache[metadataUrl] = new ConfigurationManager<OpenIdConnectConfiguration>(metadataUrl);
51 #else
52  _openIdMetadataCache[metadataUrl] = new ConfigurationManager<OpenIdConnectConfiguration>(metadataUrl, new OpenIdConnectConfigurationRetriever());
53 #endif
54 
55  _openIdMetadata = _openIdMetadataCache[metadataUrl];
56  }
57 
58  public async Task<ClaimsIdentity> GetIdentityAsync(HttpRequestMessage request)
59  {
60  if (request.Headers.Authorization != null)
61  return await GetIdentityAsync(request.Headers.Authorization.Scheme, request.Headers.Authorization.Parameter).ConfigureAwait(false);
62  return null;
63  }
64 
65  public async Task<ClaimsIdentity> GetIdentityAsync(string authorizationHeader)
66  {
67  if (authorizationHeader == null)
68  return null;
69 
70  string[] parts = authorizationHeader?.Split(' ');
71  if (parts.Length == 2)
72  return await GetIdentityAsync(parts[0], parts[1]).ConfigureAwait(false);
73  return null;
74  }
75 
76  public async Task<ClaimsIdentity> GetIdentityAsync(string scheme, string parameter)
77  {
78  // No header in correct scheme or no token
79  if (scheme != "Bearer" || string.IsNullOrEmpty(parameter))
80  return null;
81 
82  // Issuer isn't allowed? No need to check signature
83  if (!HasAllowedIssuer(parameter))
84  return null;
85 
86  try
87  {
88  ClaimsPrincipal claimsPrincipal = await ValidateTokenAsync(parameter).ConfigureAwait(false);
89  return claimsPrincipal.Identities.OfType<ClaimsIdentity>().FirstOrDefault();
90  }
91  catch (Exception e)
92  {
93 #if NET45
94  Trace.TraceWarning("Invalid token. " + e.ToString());
95 #else
96  IdentityModel.Logging.LogHelper.LogException<Exception>($"Invalid token. {e.ToString()}");
97 #endif
98  return null;
99  }
100  }
101 
102  private bool HasAllowedIssuer(string jwtToken)
103  {
104  JwtSecurityToken token = new JwtSecurityToken(jwtToken);
105  if (_tokenValidationParameters.ValidIssuer != null && _tokenValidationParameters.ValidIssuer == token.Issuer)
106  return true;
107 
108  if ((_tokenValidationParameters.ValidIssuers ?? Enumerable.Empty<string>()).Contains(token.Issuer))
109  return true;
110 
111  return false;
112  }
113 
114 
115 
116  public string GetAppIdFromClaimsIdentity(ClaimsIdentity identity)
117  {
118  if (identity == null)
119  return null;
120 
121  Claim botClaim = identity.Claims.FirstOrDefault(c => _tokenValidationParameters.ValidIssuers.Contains(c.Issuer) && c.Type == "aud");
122  return botClaim?.Value;
123  }
124 
125  public string GetAppIdFromEmulatorClaimsIdentity(ClaimsIdentity identity)
126  {
127  if (identity == null)
128  return null;
129 
130  Claim appIdClaim = identity.Claims.FirstOrDefault(c => _tokenValidationParameters.ValidIssuers.Contains(c.Issuer) && c.Type == "appid");
131  if (appIdClaim == null)
132  return null;
133 
134  // v3.1 emulator token
135  if (identity.Claims.Any(c => c.Type == "aud" && c.Value == appIdClaim.Value))
136  return appIdClaim.Value;
137 
138  // v3.0 emulator token -- allow this
139  if (identity.Claims.Any(c => c.Type == "aud" && c.Value == "https://graph.microsoft.com"))
140  return appIdClaim.Value;
141 
142  return null;
143  }
144 
145  private async Task<ClaimsPrincipal> ValidateTokenAsync(string jwtToken)
146  {
147  // _openIdMetadata only does a full refresh when the cache expires every 5 days
148  OpenIdConnectConfiguration config = null;
149  try
150  {
151  config = await _openIdMetadata.GetConfigurationAsync().ConfigureAwait(false);
152  }
153  catch (Exception e)
154  {
155 #if NET45
156  Trace.TraceError($"Error refreshing OpenId configuration: {e}");
157 #else
158  IdentityModel.Logging.LogHelper.LogException<Exception>($"Error refreshing OpenId configuration: {e}");
159 #endif
160 
161  // No config? We can't continue
162  if (config == null)
163  throw;
164  }
165 
166  // Update the signing tokens from the last refresh
167 #if NET45
168  _tokenValidationParameters.IssuerSigningTokens = config.SigningTokens;
169 #else
170  _tokenValidationParameters.IssuerSigningKeys = config.SigningKeys;
171 #endif
172 
173  JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
174 
175  try
176  {
177  SecurityToken parsedToken;
178  ClaimsPrincipal principal = tokenHandler.ValidateToken(jwtToken, _tokenValidationParameters, out parsedToken);
179  return principal;
180  }
181  catch (SecurityTokenSignatureKeyNotFoundException)
182  {
183 #if NET45
184  string keys = string.Join(", ", ((config?.SigningTokens) ?? Enumerable.Empty<SecurityToken>()).Select(t => t.Id));
185  Trace.TraceError("Error finding key for token. Available keys: " + keys);
186 #else
187  string keys = string.Join(", ", ((config?.SigningKeys) ?? Enumerable.Empty<SecurityKey>()).Select(t => t.KeyId));
188  IdentityModel.Logging.LogHelper.LogException<SecurityTokenSignatureKeyNotFoundException>("Error finding key for token.Available keys: " + keys);
189 #endif
190  throw;
191  }
192  }
193  }
194 }
JwtTokenExtractor(TokenValidationParameters tokenValidationParameters, string metadataUrl)
async Task< ClaimsIdentity > GetIdentityAsync(string authorizationHeader)
string GetAppIdFromClaimsIdentity(ClaimsIdentity identity)
async Task< ClaimsIdentity > GetIdentityAsync(HttpRequestMessage request)
async Task< ClaimsIdentity > GetIdentityAsync(string scheme, string parameter)
string GetAppIdFromEmulatorClaimsIdentity(ClaimsIdentity identity)