2
votes

I'm trying to get calendars from Office 365 to use them in a REST API (WEB API 2). I already tried a lot of stuff to generate the JWT, but for each try, I get another error.

My application is properly registred in Azure AAD, the public key is uploaded.

The last thing I tried was from this article : https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/

In his example, I can generate the JWT from two differents ways, but I get the error : x-ms-diagnostics: 2000003;reason="The audience claim value is invalid 'https://outlook.office365.com'.";error_category="invalid_resource"

Here is my code :

`

string tenantId = ConfigurationManager.AppSettings.Get("ida:TenantId");
            /**
             * use the tenant specific endpoint for requesting the app-only access token
             */
            string tokenIssueEndpoint = "https://login.windows.net/" + tenantId + "/oauth2/authorize";
            string clientId = ConfigurationManager.AppSettings.Get("ida:ClientId");




            /**
             * sign the assertion with the private key
             */
            String certPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/cert.pfx");
            X509Certificate2 cert = new X509Certificate2(
                certPath,
                "lol",
                X509KeyStorageFlags.MachineKeySet);

            /**
             * Example building assertion using Json Tokenhandler. 
             * Sort of cheating, but just if someone wonders ... there are always more ways to do something :-)
             */
            Dictionary<string, string> claims = new Dictionary<string, string>()
            {
                { "sub", clientId },
                { "jti", Guid.NewGuid().ToString() },
            };

            JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
            X509SigningCredentials signingCredentials = new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256Signature, SecurityAlgorithms.Sha256Digest);

            JwtSecurityToken selfSignedToken = new JwtSecurityToken(
                clientId,
                tokenIssueEndpoint,
                claims.Select(c => new Claim(c.Key, c.Value)),
                DateTime.UtcNow,
                DateTime.UtcNow.Add(TimeSpan.FromMinutes(15)),
                signingCredentials);

            string signedAssertion = tokenHandler.WriteToken(selfSignedToken);

            //---- End example with Json Tokenhandler... now to the fun part doing it all ourselves ...

            /**
              * Example building assertion from scratch with Crypto APIs
            */
            JObject clientAssertion = new JObject();
            clientAssertion.Add("aud", "https://outlook.office365.com");
            clientAssertion.Add("iss", clientId);
            clientAssertion.Add("sub", clientId);
            clientAssertion.Add("jti", Guid.NewGuid().ToString());
            clientAssertion.Add("scp", "Calendars.Read");
            clientAssertion.Add("nbf", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(-5)));
            clientAssertion.Add("exp", WebConvert.EpocTime(DateTime.UtcNow + TimeSpan.FromMinutes(15)));

            string assertionPayload = clientAssertion.ToString(Newtonsoft.Json.Formatting.None);

            X509AsymmetricSecurityKey x509Key = new X509AsymmetricSecurityKey(cert);
            RSACryptoServiceProvider rsa = x509Key.GetAsymmetricAlgorithm(SecurityAlgorithms.RsaSha256Signature, true) as RSACryptoServiceProvider;
            RSACryptoServiceProvider newRsa = GetCryptoProviderForSha256(rsa);
            SHA256Cng sha = new SHA256Cng();

            JObject header = new JObject(new JProperty("alg", "RS256"));
            string thumbprint = WebConvert.Base64UrlEncoded(WebConvert.HexStringToBytes(cert.Thumbprint));
            header.Add(new JProperty("x5t", thumbprint));

            string encodedHeader = WebConvert.Base64UrlEncoded(header.ToString());
            string encodedPayload = WebConvert.Base64UrlEncoded(assertionPayload);

            string signingInput = String.Concat(encodedHeader, ".", encodedPayload);

            byte[] signature = newRsa.SignData(Encoding.UTF8.GetBytes(signingInput), sha);

            signedAssertion = string.Format("{0}.{1}.{2}",
                encodedHeader,
                encodedPayload,
                WebConvert.Base64UrlEncoded(signature));

`

My JWT looks like this :

`

{
 alg: "RS256",
 x5t: "8WkmVEiCU9mHkshRp65lyowGOAk"
}.
{
 aud: "https://outlook.office365.com",
 iss: "clientId",
 sub: "clientId",
 jti: "38a34d8a-0764-434f-8e1d-c5774cf37007",
 scp: "Calendars.Read",
 nbf: 1512977093,
 exp: 1512978293
}

`

I put this token in the Authorization header after the "Bearer" string.

Any ideas to solve this kind of issue ? I guess I need a external point of view :)

Thanks

2

2 Answers

1
votes

You do not generate the JWT, Azure AD does that.

You would use your certificate to get the access token. Example borrowed from article you linked:

string authority = appConfig.AuthorizationUri.Replace("common", tenantId);
AuthenticationContext authenticationContext = new AuthenticationContext(
               authority,
               false);

string certfile = Server.MapPath(appConfig.ClientCertificatePfx);

X509Certificate2 cert = new X509Certificate2(
    certfile,
    appConfig.ClientCertificatePfxPassword, // password for the cert file containing private key
    X509KeyStorageFlags.MachineKeySet);

ClientAssertionCertificate cac = new ClientAssertionCertificate(
    appConfig.ClientId, cert);

var authenticationResult = await authenticationContext.AcquireTokenAsync(
     resource,   // always https://outlook.office365.com for Mail, Calendar, Contacts API
     cac);
return authenticationResult.AccessToken;

The resulting access token can then be attached to the request to the API.

The reason it does not work is that the Outlook API does not consider you a valid token issuer. It will only accept tokens signed with Azure AD's private key. Which you obviously do not have.

The private key from the key pair you generated can only be used to authenticate your app to Azure AD.

0
votes

Thanks juunas !

This is the working code :

var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/tenantId");

            string tenantId = ConfigurationManager.AppSettings.Get("ida:TenantId");
            string clientId = ConfigurationManager.AppSettings.Get("ida:ClientId");

            String certPath = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/cert.pfx");
            X509Certificate2 cert = new X509Certificate2(
                certPath,
                "keyPwd",
                X509KeyStorageFlags.MachineKeySet);

            ClientAssertionCertificate cac = new ClientAssertionCertificate(clientId, cert);

            var result = (AuthenticationResult)authContext
                .AcquireTokenAsync("https://outlook.office.com", cac)
                .Result;
            var token = result.AccessToken;

            return token;

Other required step for App-only token, you must use the Grant Permissions button in AAD application settings.