summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.AspNet
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOpenAuth.AspNet')
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs75
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs457
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADGraph.cs61
-rw-r--r--src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs49
-rw-r--r--src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj4
5 files changed, 646 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs
new file mode 100644
index 0000000..deb396f
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClaims.cs
@@ -0,0 +1,75 @@
+//-----------------------------------------------------------------------
+// <copyright file="AzureADClaims.cs" company="Microsoft">
+// Copyright (c) Microsoft. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+
+ /// <summary>
+ /// Contains clains of a AzureAD token.
+ /// </summary>
+ /// <remarks>
+ /// Technically, this class doesn't need to be public, but because we want to make it serializable in medium trust, it has to be public.
+ /// </remarks>
+ [DataContract]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")]
+ public class AzureADClaims {
+ #region Public Properties
+
+ /// <summary>
+ /// Gets or sets the audience.
+ /// </summary>
+ /// <value> The audience token is valid for. </value>
+ [DataMember(Name = "aud")]
+ public string Aud { get; set; }
+
+ /// <summary>
+ /// Gets or sets the issuer.
+ /// </summary>
+ /// <value> The issuer. </value>
+ [DataMember(Name = "iss")]
+ public string Iss { get; set; }
+
+ /// <summary>
+ /// Gets or sets the early expiry time.
+ /// </summary>
+ /// <value> The early expiry time. </value>
+ [DataMember(Name = "nbf")]
+ public string Nbf { get; set; }
+
+ /// <summary>
+ /// Gets or sets the expiry time.
+ /// </summary>
+ /// <value> The expiry time. </value>
+ [DataMember(Name = "exp")]
+ public string Exp { get; set; }
+
+ /// <summary>
+ /// Gets or sets the id of the user.
+ /// </summary>
+ /// <value> The id of the user. </value>
+ [DataMember(Name = "oid")]
+ public string Oid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the id of the tenant.
+ /// </summary>
+ /// <value> The tenant . </value>
+ [DataMember(Name = "tid")]
+ public string Tid { get; set; }
+
+ /// <summary>
+ /// Gets or sets the appid of application.
+ /// </summary>
+ /// <value> The id of the application. </value>
+ [DataMember(Name = "appid")]
+ public string Appid { get; set; }
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs
new file mode 100644
index 0000000..6ff93e7
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADClient.cs
@@ -0,0 +1,457 @@
+//-----------------------------------------------------------------------
+// <copyright file="AzureADClient.cs" company="Microsoft">
+// Copyright (c) Microsoft. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics.CodeAnalysis;
+ using System.IdentityModel.Tokens;
+ using System.IO;
+ using System.Net;
+ using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Text;
+ using System.Web;
+ using System.Web.Script.Serialization;
+ using System.Xml;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// The AzureAD client.
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")]
+ public sealed class AzureADClient : OAuth2Client {
+ #region Constants and Fields
+
+ /// <summary>
+ /// The authorization endpoint.
+ /// </summary>
+ private const string AuthorizationEndpoint = "https://login.windows.net/global/oauth2/authorize";
+
+ /// <summary>
+ /// The token endpoint.
+ /// </summary>
+ private const string TokenEndpoint = "https://login.windows.net/global/oauth2/token";
+
+ /// <summary>
+ /// The name of the graph resource.
+ /// </summary>
+ private const string GraphResource = "00000002-0000-0000-c000-000000000000/graph.windows.net";
+
+ /// <summary>
+ /// The URL to get the token decoding certificate from.
+ /// </summary>
+ private const string MetaDataEndpoint = "https://login.windows.net/evosts.onmicrosoft.com/FederationMetadata/2007-06/FederationMetadata.xml";
+
+ /// <summary>
+ /// The URL for AzureAD graph.
+ /// </summary>
+ private const string GraphEndpoint = "https://graph.windows.net/";
+
+ /// <summary>
+ /// The id of the STS.
+ /// </summary>
+ private const string STSName = "https://sts.windows.net";
+
+ /// <summary>
+ /// The app id.
+ /// </summary>
+ private readonly string appId;
+
+ /// <summary>
+ /// The app secret.
+ /// </summary>
+ private readonly string appSecret;
+
+ /// <summary>
+ /// The resource to target.
+ /// </summary>
+ private readonly string resource;
+
+ /// <summary>
+ /// Encoding cert.
+ /// </summary>
+ private static X509Certificate2[] encodingcert;
+
+ /// <summary>
+ /// Hash algo used by the X509Cert.
+ /// </summary>
+ private static HashAlgorithm hash;
+
+ /// <summary>
+ /// The tenantid claim for the authcode.
+ /// </summary>
+ private string tenantid;
+
+ /// <summary>
+ /// The userid claim for the authcode.
+ /// </summary>
+ private string userid;
+ #endregion
+
+ #region Constructors and Destructors
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AzureADClient"/> class.
+ /// </summary>
+ /// <param name="appId">
+ /// The app id.
+ /// </param>
+ /// <param name="appSecret">
+ /// The app secret.
+ /// </param>
+ public AzureADClient(string appId, string appSecret)
+ : this(appId, appSecret, GraphResource) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AzureADClient"/> class.
+ /// </summary>
+ /// <param name="appId">
+ /// The app id.
+ /// </param>
+ /// <param name="appSecret">
+ /// The app secret.
+ /// </param>
+ /// <param name="resource">
+ /// The resource of oauth request.
+ /// </param>
+ public AzureADClient(string appId, string appSecret, string resource)
+ : base("azuread") {
+ Requires.NotNullOrEmpty(appId, "appId");
+ Requires.NotNullOrEmpty(appSecret, "appSecret");
+ Requires.NotNullOrEmpty(resource, "resource");
+ this.appId = appId;
+ this.appSecret = appSecret;
+ this.resource = resource;
+ }
+ #endregion
+
+ #region Methods
+
+ /// <summary>
+ /// The get service login url.
+ /// </summary>
+ /// <param name="returnUrl">
+ /// The return url.
+ /// </param>
+ /// <returns>An absolute URI.</returns>
+ protected override Uri GetServiceLoginUrl(Uri returnUrl) {
+ var builder = new UriBuilder(AuthorizationEndpoint);
+ builder.AppendQueryArgs(
+ new Dictionary<string, string> {
+ { "client_id", this.appId },
+ { "redirect_uri", returnUrl.AbsoluteUri },
+ { "response_type", "code" },
+ { "resource", this.resource },
+ });
+ return builder.Uri;
+ }
+
+ /// <summary>
+ /// The get user data.
+ /// </summary>
+ /// <param name="accessToken">
+ /// The access token.
+ /// </param>
+ /// <returns>A dictionary of profile data.</returns>
+ protected override IDictionary<string, string> GetUserData(string accessToken) {
+ IDictionary<string, string> userData = new Dictionary<string, string>();
+ try {
+ AzureADGraph graphData;
+ WebRequest request =
+ WebRequest.Create(
+ GraphEndpoint + this.tenantid + "/users/" + this.userid + "?api-version=0.9");
+ request.Headers = new WebHeaderCollection();
+ request.Headers.Add("authorization", accessToken);
+ using (var response = request.GetResponse()) {
+ using (var responseStream = response.GetResponseStream()) {
+ graphData = JsonHelper.Deserialize<AzureADGraph>(responseStream);
+ }
+ }
+
+ // this dictionary must contains
+ userData.AddItemIfNotEmpty("id", graphData.ObjectId);
+ userData.AddItemIfNotEmpty("username", graphData.UserPrincipalName);
+ userData.AddItemIfNotEmpty("name", graphData.DisplayName);
+
+ return userData;
+ } catch (Exception e) {
+ System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive());
+ return userData;
+ }
+ }
+
+ /// <summary>
+ /// Obtains an access token given an authorization code and callback URL.
+ /// </summary>
+ /// <param name="returnUrl">
+ /// The return url.
+ /// </param>
+ /// <param name="authorizationCode">
+ /// The authorization code.
+ /// </param>
+ /// <returns>
+ /// The access token.
+ /// </returns>
+ protected override string QueryAccessToken(Uri returnUrl, string authorizationCode) {
+ try {
+ var entity =
+ MessagingUtilities.CreateQueryString(
+ new Dictionary<string, string> {
+ { "client_id", this.appId },
+ { "redirect_uri", returnUrl.AbsoluteUri },
+ { "client_secret", this.appSecret },
+ { "code", authorizationCode },
+ { "grant_type", "authorization_code" },
+ });
+
+ WebRequest tokenRequest = WebRequest.Create(TokenEndpoint);
+ tokenRequest.ContentType = "application/x-www-form-urlencoded";
+ tokenRequest.ContentLength = entity.Length;
+ tokenRequest.Method = "POST";
+
+ using (Stream requestStream = tokenRequest.GetRequestStream()) {
+ var writer = new StreamWriter(requestStream);
+ writer.Write(entity);
+ writer.Flush();
+ }
+
+ HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
+ if (tokenResponse.StatusCode == HttpStatusCode.OK) {
+ using (Stream responseStream = tokenResponse.GetResponseStream()) {
+ var tokenData = JsonHelper.Deserialize<OAuth2AccessTokenData>(responseStream);
+ if (tokenData != null) {
+ AzureADClaims claimsAD;
+ claimsAD = this.ParseAccessToken(tokenData.AccessToken, true);
+ if (claimsAD != null) {
+ this.tenantid = claimsAD.Tid;
+ this.userid = claimsAD.Oid;
+ return tokenData.AccessToken;
+ }
+ return string.Empty;
+ }
+ }
+ }
+
+ return null;
+ } catch (Exception e) {
+ System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive());
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Base64 decode function except that it switches -_ to +/ before base64 decode
+ /// </summary>
+ /// <param name="str">
+ /// The string to be base64urldecoded.
+ /// </param>
+ /// <returns>
+ /// Decoded string as string using UTF8 encoding.
+ /// </returns>
+ private static string Base64URLdecode(string str) {
+ System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
+ return encoder.GetString(Base64URLdecodebyte(str));
+ }
+
+ /// <summary>
+ /// Base64 decode function except that it switches -_ to +/ before base64 decode
+ /// </summary>
+ /// <param name="str">
+ /// The string to be base64urldecoded.
+ /// </param>
+ /// <returns>
+ /// Decoded string as bytes.
+ /// </returns>
+ private static byte[] Base64URLdecodebyte(string str) {
+ // First replace chars and then pad per spec
+ str = str.Replace('-', '+').Replace('_', '/');
+ str = str.PadRight(str.Length + ((4 - (str.Length % 4)) % 4), '=');
+ return Convert.FromBase64String(str);
+ }
+
+ /// <summary>
+ /// Validate whether the unsigned value is same as signed value
+ /// </summary>
+ /// <param name="uval">
+ /// The raw input of the string signed using the key
+ /// </param>
+ /// <param name="sval">
+ /// The signature of the string
+ /// </param>
+ /// <param name="certthumb">
+ /// The thumbprint of cert used to encrypt token
+ /// </param>
+ /// <returns>
+ /// True if same, false otherwise.
+ /// </returns>
+ private static bool ValidateSig(byte[] uval, byte[] sval, byte[] certthumb) {
+ try {
+ bool ret = false;
+
+ X509Certificate2[] certx509 = GetEncodingCert();
+ string certthumbhex = string.Empty;
+
+ // Get the hexadecimail representation of the certthumbprint
+ for (int i = 0; i < certthumb.Length; i++) {
+ certthumbhex += certthumb[i].ToString("X2");
+ }
+
+ for (int c = 0; c < certx509.Length; c++) {
+ // Skip any cert that does not have the same thumbprint as token
+ if (certx509[c].Thumbprint.ToLower() != certthumbhex.ToLower()) {
+ continue;
+ }
+ X509SecurityToken tok = new X509SecurityToken(certx509[c]);
+ if (tok == null) {
+ return false;
+ }
+ for (int i = 0; i < tok.SecurityKeys.Count; i++) {
+ X509AsymmetricSecurityKey key = tok.SecurityKeys[i] as X509AsymmetricSecurityKey;
+ RSACryptoServiceProvider rsa = key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, false) as RSACryptoServiceProvider;
+
+ if (rsa == null) {
+ continue;
+ }
+ ret = rsa.VerifyData(uval, hash, sval);
+ if (ret == true) {
+ return ret;
+ }
+ }
+ }
+ return ret;
+ } catch (CryptographicException e) {
+ Console.WriteLine(e.ToStringDescriptive());
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns the certificate with which the token is encoded.
+ /// </summary>
+ /// <returns>
+ /// The encoding certificate.
+ /// </returns>
+ private static X509Certificate2[] GetEncodingCert() {
+ if (encodingcert != null) {
+ return encodingcert;
+ }
+ try {
+ // Lock for exclusive access
+ lock (typeof(AzureADClient)) {
+ XmlDocument doc = new XmlDocument();
+
+ WebRequest request =
+ WebRequest.Create(MetaDataEndpoint);
+ using (WebResponse response = request.GetResponse()) {
+ using (Stream responseStream = response.GetResponseStream()) {
+ doc.Load(responseStream);
+ XmlNodeList list = doc.GetElementsByTagName("X509Certificate");
+ encodingcert = new X509Certificate2[list.Count];
+ for (int i = 0; i < list.Count; i++) {
+ byte[] todecode_byte = Convert.FromBase64String(list[i].InnerText);
+ encodingcert[i] = new X509Certificate2(todecode_byte);
+ }
+ if (hash == null) {
+ hash = SHA256.Create();
+ }
+ }
+ }
+ }
+ return encodingcert;
+ } catch (Exception e) {
+ System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive());
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Parses the access token into an AzureAD token.
+ /// </summary>
+ /// <param name="token">
+ /// The token as a string.
+ /// </param>
+ /// <param name="validate">
+ /// Whether to validate against time\audience.
+ /// </param>
+ /// <returns>
+ /// The claims as an object and null in case of failure.
+ /// </returns>
+ private AzureADClaims ParseAccessToken(string token, bool validate) {
+ try {
+ // This is the encoded JWT token split into the 3 parts
+ string[] strparts = token.Split('.');
+
+ // Decparts has the header and claims section decoded from JWT
+ string jwtHeader, jwtClaims;
+ string jwtb64Header, jwtb64Claims, jwtb64Sig;
+ byte[] jwtSig;
+ if (strparts.Length != 3) {
+ return null;
+ }
+ jwtb64Header = strparts[0];
+ jwtb64Claims = strparts[1];
+ jwtb64Sig = strparts[2];
+ jwtHeader = Base64URLdecode(jwtb64Header);
+ jwtClaims = Base64URLdecode(jwtb64Claims);
+ jwtSig = Base64URLdecodebyte(jwtb64Sig);
+
+ JavaScriptSerializer s1 = new JavaScriptSerializer();
+
+ AzureADClaims claimsAD = s1.Deserialize<AzureADClaims>(jwtClaims);
+ AzureADHeader headerAD = s1.Deserialize<AzureADHeader>(jwtHeader);
+
+ if (validate) {
+ // Check to see if the token is valid
+ // Check if its JWT and RSA encoded
+ if (headerAD.Typ.ToUpper() != "JWT") {
+ return null;
+ }
+
+ // Check if its JWT and RSA encoded
+ if (headerAD.Alg.ToUpper() != "RS256") {
+ return null;
+ }
+ if (string.IsNullOrEmpty(headerAD.X5t)) {
+ return null;
+ }
+
+ // Check audience to be graph
+ if (claimsAD.Aud.ToLower().ToLower() != GraphResource.ToLower()) {
+ return null;
+ }
+
+ // Check issuer to be sts
+ if (claimsAD.Iss.ToLower().IndexOf(STSName.ToLower()) != 0) {
+ return null;
+ }
+
+ // Check time validity
+ TimeSpan span = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc));
+ double secsnow = span.TotalSeconds;
+ double nbfsecs = Convert.ToDouble(claimsAD.Nbf);
+ double expsecs = Convert.ToDouble(claimsAD.Exp);
+ if ((nbfsecs - 100 > secsnow) || (secsnow > expsecs + 100)) {
+ return null;
+ }
+
+ // Validate the signature of the token
+ string tokUnsigned = jwtb64Header + "." + jwtb64Claims;
+ if (!ValidateSig(Encoding.UTF8.GetBytes(tokUnsigned), jwtSig, Base64URLdecodebyte(headerAD.X5t))) {
+ return null;
+ }
+ }
+ return claimsAD;
+ } catch (Exception e) {
+ System.Diagnostics.Debug.WriteLine(e.ToStringDescriptive());
+ return null;
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADGraph.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADGraph.cs
new file mode 100644
index 0000000..8269419
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADGraph.cs
@@ -0,0 +1,61 @@
+//-----------------------------------------------------------------------
+// <copyright file="AzureADGraph.cs" company="Microsoft">
+// Copyright (c) Microsoft. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+
+ /// <summary>
+ /// Contains data of a AzureAD user.
+ /// </summary>
+ /// <remarks>
+ /// Technically, this class doesn't need to be public, but because we want to make it serializable in medium trust, it has to be public.
+ /// </remarks>
+ [DataContract]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")]
+ public class AzureADGraph {
+ #region Public Properties
+
+ /// <summary>
+ /// Gets or sets the firstname.
+ /// </summary>
+ /// <value> The first name. </value>
+ [DataMember(Name = "givenName")]
+ public string GivenName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the lastname.
+ /// </summary>
+ /// <value> The last name. </value>
+ [DataMember(Name = "surname")]
+ public string Surname { get; set; }
+
+ /// <summary>
+ /// Gets or sets the email.
+ /// </summary>
+ /// <value> The email. </value>
+ [DataMember(Name = "userPrincipalName")]
+ public string UserPrincipalName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the fullname.
+ /// </summary>
+ /// <value> The fullname. </value>
+ [DataMember(Name = "displayName")]
+ public string DisplayName { get; set; }
+
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value> The id. </value>
+ [DataMember(Name = "objectId")]
+ public string ObjectId { get; set; }
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs
new file mode 100644
index 0000000..042eccb
--- /dev/null
+++ b/src/DotNetOpenAuth.AspNet/Clients/OAuth2/AzureADHeader.cs
@@ -0,0 +1,49 @@
+//-----------------------------------------------------------------------
+// <copyright file="AzureADHeader.cs" company="Microsoft">
+// Copyright (c) Microsoft. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.AspNet.Clients {
+ using System;
+ using System.ComponentModel;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.Serialization;
+
+ /// <summary>
+ /// Contains header of AzureAD JWT token.
+ /// </summary>
+ /// <remarks>
+ /// Technically, this class doesn't need to be public, but because we want to make it serializable in medium trust, it has to be public.
+ /// </remarks>
+ [DataContract]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "AzureAD", Justification = "Brand name")]
+
+ public class AzureADHeader {
+ #region Public Properties
+
+ /// <summary>
+ /// Gets or sets the type of token. Will always be JWT
+ /// </summary>
+ /// <value> The type of token. </value>
+ [DataMember(Name = "typ")]
+ public string Typ { get; set; }
+
+ /// <summary>
+ /// Gets or sets the algo of the header.
+ /// </summary>
+ /// <value> The algo of encoding. </value>
+ [DataMember(Name = "alg")]
+ public string Alg { get; set; }
+
+ /// <summary>
+ /// Gets or sets the thumbprint of the header.
+ /// </summary>
+ /// <value> The thumbprint of the cert used to encode. </value>
+ [DataMember(Name = "x5t")]
+ public string X5t { get; set; }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
index 405ac3c..ea87cfd 100644
--- a/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
+++ b/src/DotNetOpenAuth.AspNet/DotNetOpenAuth.AspNet.csproj
@@ -44,6 +44,10 @@
<ItemGroup>
<Compile Include="AuthenticationResult.cs" />
<Compile Include="Clients\DictionaryExtensions.cs" />
+ <Compile Include="Clients\OAuth2\AzureADClaims.cs" />
+ <Compile Include="Clients\OAuth2\AzureADClient.cs" />
+ <Compile Include="Clients\OAuth2\AzureADGraph.cs" />
+ <Compile Include="Clients\OAuth2\AzureADHeader.cs" />
<Compile Include="Clients\OAuth2\WindowsLiveClient.cs" />
<Compile Include="Clients\OAuth\AuthenticationOnlyCookieOAuthTokenManager.cs">
<SubType>Code</SubType>