Office 365 API authentication form REST API - c#

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

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.

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.

Related

Cognito refresh token with secret key causes unable to verify secret hash

I am using below code to refresh token in an AWS Cognito application configured with secret key. No matter which configuration I have tried it always causes common issue of unable to verify secret hash. The input parameters have been trippled checked and the login functionality works well. Can anyone advice on what is the issue with the below codeset.
public static async Task<AuthFlowResponse> GetRefreshAsync(string userName, string refreshToken)
{
AmazonCognitoIdentityProviderClient provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), FallbackRegionFactory.GetRegionEndpoint());
CognitoUserPool userPool = new CognitoUserPool(userPoolId, clientId, provider, clientSecret);
CognitoUser user = new CognitoUser(userName, clientId, userPool, provider, clientSecret);
user.SessionTokens = new CognitoUserSession(null, null, refreshToken, DateTime.Now, DateTime.Now.AddHours(1));
InitiateRefreshTokenAuthRequest refreshRequest = new InitiateRefreshTokenAuthRequest()
{
AuthFlowType = AuthFlowType.REFRESH_TOKEN_AUTH
};
return await user.StartWithRefreshTokenAuthAsync(refreshRequest).ConfigureAwait(false);
}

Get Token from Azure using AAD App (ClientID, TenantID, Cert-Thumbprint)

I have below Method to get a token from Azure using ClientID, TenantID and AADAppPassword
This is working awesome but now I need to switch to different AAD AppID and use Certificate Thumbprint Or Certificate pfx.
I don't want to change my 1000+ lines of code.
Can someone help me get a token the same way I'm getting using below Method but use Certificate Thumbprint instead and which returns token so that I can call the method right before I'm about to make rest API call.
public static async Task<string> GetAccessToken(string tenantId, string clientId, string clientKey)
{
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientKey);
var result = await authenticationContext
.AcquireTokenAsync("https://management.azure.com/", credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
return token;
}
You must use ClientAssertionCertificate instead of ClientCredential
X509Certificate2 cert = ReadCertificateFromStore(config.CertName);
certCred = new ClientAssertionCertificate(config.ClientId, cert);
result = await authContext.AcquireTokenAsync(todoListResourceId, certCred);
You may refer the Azure AD v1 Sample for this.
MSAL.NET is now the recommended auth library to use with the Microsoft identity platform. No new features will be implemented on ADAL.NET. The efforts are focused on improving MSAL. You can refer the documentation here if you are planning to migrate applications to MSAL.NET

Azure Functions: How to access the host key programmatically using C# code?

I'm writing an Azure Function App that needs to provide a HTML page with a form. The form should post back to one of the endpoints of the App. The App shall be protected with Azure's Authorization Key functionality.
Azure allows the caller to provide his/her key in two ways:
With a code request query parameter.
With a x-functions-clientid HTTP header.
To make the call to the other endpoint of the Azure Function App successful, I will therefore need to provide the Host Key along with my request. For example like this:
<form method='post' action='DoSomething?code={{{WHERE TO GET THIS FROM?}}}'>
<input name='someInput' />
<input type='submit' />
</form>
I'm using C# to generate the HTML code. What's the most bullet-proof way to get the/a Host Key programmatically?
You could use Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that.
Refer this SO thread for more information.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}

C# Get Access Token for Azure AD Identity

I try to get an access token for an identity to get data from all users profiles. I'm using OpenID connect to authenticate the user, in which I succeeded. I'm also able to get an access token, but it is not valid.
The code I'm using:
To authenticate:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = AppVar.ClientId,
ClientSecret = AppVar.ClientSecret,
Authority = AppVar.AzureADAuthority,
RedirectUri = "https://localhost:44326/",
ResponseType = "code id_token",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) => {
var code = context.Code;
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
ADALTokenCache cache = new ADALTokenCache(signedInUserID);
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), cache);
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, AppVar.AzureResource);
return Task.FromResult(0);
}
}
});
To acquire an access token for https://graph.microsoft.com
public ActionResult Index()
{
string usrObjectId = ClaimsPrincipal.Current.FindFirst(AppVar.ClaimTypeObjectIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(AppVar.AzureADAuthority, new ADALTokenCache(usrObjectId));
ClientCredential credential = new ClientCredential(AppVar.ClientId, AppVar.ClientSecret);
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);
request.AddHeader("Cache-Control", "no-cache");
request.AddHeader("Authorization", "Bearer " + res.AccessToken);
IRestResponse response = client.Execute(request);
return View();
}
But when I execute the request, I get:
{
"error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "1cc9e532-bd31-4ca5-8f1d-2d0796883c2e",
"date": "2018-10-17T06:50:35"
}
}
}
What am I doing wrong?
I had the same issue. Use following code which I have used to get the Access Token from Azure AD. Just Login to your Azure portal and find your Tenant ID and Client ID and paste it to the following code. It works perfectly for me.
namespace TokenGenerator
{
class Program
{
private static string token = string.Empty;
static void Main(string[] args)
{
//Get an authentication access token
token = GetToken();
}
#region Get an authentication access token
private static string GetToken()
{
// TODO: Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.21.301221612
// and add using Microsoft.IdentityModel.Clients.ActiveDirectory
//The client id that Azure AD created when you registered your client app.
string clientID = "Your client ID";
string AuthEndPoint = "https://login.microsoftonline.com/{0}/oauth2/token";
string TenantId = "Your Tenant ID";
//RedirectUri you used when you register your app.
//For a client app, a redirect uri gives Azure AD more details on the application that it will authenticate.
// You can use this redirect uri for your client app
string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient";
//Resource Uri for Power BI API
string resourceUri = "https://analysis.windows.net/powerbi/api";
//Get access token:
// To call a Power BI REST operation, create an instance of AuthenticationContext and call AcquireToken
// AuthenticationContext is part of the Active Directory Authentication Library NuGet package
// To install the Active Directory Authentication Library NuGet package in Visual Studio,
// run "Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory" from the nuget Package Manager Console.
// AcquireToken will acquire an Azure access token
// Call AcquireToken to get an Azure token from Azure Active Directory token issuance endpoint
string authority = string.Format(CultureInfo.InvariantCulture, AuthEndPoint, TenantId);
AuthenticationContext authContext = new AuthenticationContext(authority);
string token = authContext.AcquireTokenAsync(resourceUri, clientID, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto)).Result.AccessToken;
Console.WriteLine(token);
Console.ReadLine();
return token;
}
#endregion
}
}
Looking at your error, since it's failing at token validation my guess would be that it's related to audience for which the token was acquired.
You're calling the https://graph.microsoft.com endpoint, so make sure that is the exact value for the resource.
Specifically in this code, make sure AppVar.AzureResource gets a value https://graph.microsoft.com
AuthenticationResult res = authContext.AcquireToken(AppVar.AzureResource, credential);
var client = new RestClient("https://graph.microsoft.com/v1.0/users/?$select=userPrincipalName,displayName,mobilePhone");
var request = new RestRequest(Method.GET);

decrypting JWT to Authentication Ticket with C# JwtFormat.Unprotect

I have a webapi which is authenticated using JWT tokens.
I validate using a custom JWT protection. This is as follows;
private const string AudiencePropertyKey = "as:client_id";
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
Client client = null;
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include the client_id");
using (AuthRepository _repo = new AuthRepository())
{
client = _repo.FindClient(audienceId);
}
if (client == null) throw new InvalidOperationException("ClientId does not exist.");
string symmetricKeyAsBase64 = client.Secret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
Access is controlled through a database table, so the user sends across their clientid as a part of the auth service. they are rejected if the clientid doesn't exist in the database, and the ticket is encoded using the secret associated with this db entry and returned to them.
Now I am struggling with the decoding of the JWT on data requests? Now the JWT decodes just fine on jwt.io so I assume there must be someway of decoding using the JwtProtect without requiring a store on the currently dished out JWT tokens? As far as I can see the JwtProtect wants to have the Allowed audiences passed across? (I could so this by returning all from the db but is it really necessary?).
JWT token is just a base64 string, you can freely decode it in multiple ways.
If you want to "unprotect" and validate the ticket you can use System.IdentityModel.Tokens.SecurityTokenHandler.
Check this answer for an example.
BTW, just a personal consideration: the first rule in security is "do not make your own security, but stick with the mass". You will find that following a clear and used path will offer you more support and you will be sure not to mess or to forget something important.

Categories