Office 365, oauth2, where do I find Key+Secret? - azure

I'm trying for the first time to get access to 365 using oauth2, for my Native Application.
I have registered my application in Azure AD.
The documentation says, "...In the Azure Management Portal, select your application and choose Configure in the top menu. Scroll down to keys."
But in (my) Azure Application, Configure properties, I only have Name, Client ID, URL and Logo, and the Permissions area - No "Keys" area.
Am I missing something?

Web Application And/OR Web API
As tou are looking for KEYS , You need to create your application in AD as Web Application or web API
then you can find the Key and secret.
Native Client Application
If you're developing a native client app, you don't need the keys because this auth flow doesn't require them.
So first of all you need to use ADAL (Active Directory Authentication Library) use the right version for your client program.
Then you should to reference your AD configuration for the App, note there are no KEYs required.
// Native client configuration in AzureAD
private string clientID = "3dfre985df0-b45640-443-a8e5-f4bd23e9d39f368";
private Uri redirectUri = new Uri("http://myUrl.webapi.client");
Then prepare AD authority URL and create the Auth Context.
private const string authority = "https://login.windows.net/cloudalloc.com";
private AuthenticationContext authContext = new AuthenticationContext(authority);
That's all, after that you need to ask for access tokens depending on the resource you want to access.
AuthenticationResult result = null;
try
{
result = authContext.AcquireToken(resource, clientId, redirectUri, PromptBehavior.Never);
}
catch (AdalException ex)
{
if (ex.ErrorCode != "user_interaction_required")
{
// An unexpected error occurred.
MessageBox.Show(ex.Message);
}
}
the resource could be a webapi or office 365 resource URI

Related

Retrieve Azure KeyVault secret using client secret

I'm experimenting with various Azure features and currently want to retrieve a secret from KeyVault.
Straight to the case:
I'm using this nuget package to interact with my azure resources.
I've developed a simple .NET Core console app and run it locally.
I have a KeyVault resource with one secret defined which is active and not expired.
I've registered an App in AAD so my locally shipped .NET Core console app has an identity within AAD.
Than I've created a "client secret" within this registered app in AAD to use it to authenticate myself as an app.
After that I've added access policy in my KeyVault resource to allow GET operation for secrets for this registered app:
Then I've developed a small piece of code which should retrieve the desired secret:
public class AzureAuthentication
{
public async Task<string> GetAdminPasswordFromKeyVault()
{
const string clientId = "--my-client-id--";
const string tenantId = "--my-tenant-id--";
const string clientSecret = "--my-client-secret--";
var credentials = new ClientSecretCredential(tenantId, clientId, clientSecret);
var client = new SecretClient(new Uri("https://mykeyvaultresource.vault.azure.net"), credentials);
var secret = await client.GetSecretAsync("admincreds");
return secret.Value.Value;
}
}
However when I'm trying to do this I'm getting an AccessDenied error:
Am I missing something painfully obvious here? Or there is some latency (>30 min for this moment) for which changes from Access policies screen in KeyVault resource are applied?
I test your code and Get permission, it works fine.
From your screenshot, it looks you didn't add the correct service principal related to the AD App to the Access policies.
If you add the service principal related to the AD App, it will appear as APPLICATION, not COMPOUND IDENTITY.
So when you add it, you could search for the client Id(i.e. application Id) or the name of your App Registration directly, make sure you add the correct one.
Make sure your AD App(service principal) has the correct permission in your keyvault -> Access policies

Azure B2C - Use msal to get authorization to call Graph API

I recently got started working with B2C. I managed to get their sample application/API using MSAL and an API working with my own tenant.
Now I wanted to:
Figure out how I can run this sample without using an API. The sample uses Scopes to get read/write access to the API. If I remove the references to the API from the app, it no longer works. Surely there should be some way to authenticate to B2C without requiring an API? This is not really important to my application but I'm mostly curious if the webservice HAS to be there as part of the auth-process?
Communicate with Graph Api (Windows or Microsoft Graph?). The sample MS provides uses ADAL and some console application. I cannot find a sample that uses MSAL, so I am having trouble incorporating it into my own application. Is it now possible to call Graph API using MSAL? If it is, is there some documentation on how to do this somewhere?
I tried simply following the docs above and registering an app/granting it permissions. Then putting the client ID/key into my own application (the MSAL one from the first sample), but then I just get a message from B2C that looks like:
Correlation ID: 01040e7b-846c-4f81-9a0f-ff515fd00398
Timestamp: 2018-01-30 10:55:37Z
AADB2C90068: The provided application with ID '9cd938c6-d3ed-4146-aee5-a661cd7d984b' is not valid against this service. Please use an application created via the B2C portal and try again.
It's true that it's not registered via the B2C portal, but that is what the instructions say; to register it in the B2C tenant under App Registrations, not the B2c portal.
The Startup class where all the magic happens looks like:
public partial class Startup
{
// App config settings
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];
public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// B2C policy identifiers
public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicyId;
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
/*
* Catch any failures received by the authentication middleware and handle appropriately
*/
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
/*
* Callback function when an authorization code is received
*/
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
}
With regards to #2, you can only use the Azure AD Graph API with an Azure AD B2C directory, as noted in the "Azure AD B2C: Use the Azure AD Graph API" article.
Here is how (which I have copied from a previous answer)...
Azure AD B2C issues tokens using the Azure AD v2.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
The Azure AD Graph API requires tokens that are issued using the Azure AD v1.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/token
At design-time:
Register the web application using the Azure AD B2C portal.
Register the web application using the Azure AD portal and grant the Read directory data permission.
At runtime:
The web application redirects the end user to the Azure AD B2C v2.0 endpoint for sign-in. Azure AD B2C issues an ID token containing the user identifier.
The web application acquires an access token from the Azure AD v1.0 endpoint using the application credentials that were created at design-time in step 2.
The web application invokes the Azure AD Graph API, passing the user identifier that was received in step 1, with the access token that was issued in step 2, and queries the user object, etc.
This answer is just addressing question #1, I am unsure on #2 other than it is not publicly documented as far as I know.
B2C does not require you to use an API/service when building your app. In the sample you're looking at it's using 2 libraries to do slightly different (but related) things.
First, it's using an OWIN middleware module to help facilitate the authentication piece. This helps your app identify who a user is, but does not authorize your app to do perform actions or access data on their behalf. This library will net you a session with the end user and basic information about them as well as a authorization code you can use later on.
The other library being used is MSAL. MSAL is a client library for token acquisition and management. In this case after the initial authentication takes place using the aforementioned middleware, MSAL will use the authorization code to get access and refresh tokens that your app can use to call APIs. This is able to take place without end user interaction because they would have already consented to the app (and you would've configured the permissions your app needed). MSAL then manages the refresh and caching of these tokens, and makes them accessible via AcquireTokenSilent().
In order to remove the API functionality from the sample app, you'd need to do a bit more than just remove the scope. Specifically, eliminating the code in the TaskController.cs that is trying to call APIs, remove most usage of MSAL, and likely a few more things. This sample implements a Web App only architecture (Warning: it's for Azure AD not Azure AD B2C. The code is very similar, but would require a bit of modification).

Authenticating as a Service with Azure AD B2C

We have setup our application using Azure AD B2C and OAuth, this works fine, however I am trying to authenticate as a service in order to make service to service calls. I am slightly new to this, but I have followed some courses on Pluralsight on how to do this on "normal" Azure Active Directory and I can get it to work, but following the same principles with B2C does not work.
I have this quick console app:
class Program
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"]; //APIClient ApplicationId
private static string appKey = ConfigurationManager.AppSettings["ida:appKey"]; //APIClient Secret
private static string aadInstance = ConfigurationManager.AppSettings["ida:aadInstance"]; //https://login.microsoftonline.com/{0}
private static string tenant = ConfigurationManager.AppSettings["ida:tenant"]; //B2C Tenant
private static string serviceResourceId = ConfigurationManager.AppSettings["ida:serviceResourceID"]; //APP Id URI For API
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static AuthenticationContext authContext = new AuthenticationContext(authority);
private static ClientCredential clientCredential = new ClientCredential(clientId, appKey);
static void Main(string[] args)
{
AuthenticationResult result = authContext.AcquireToken(serviceResourceId, clientCredential);
Console.WriteLine("Authenticated succesfully.. making HTTPS call..");
string serviceBaseAddress = "https://localhost:44300/";
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = httpClient.GetAsync(serviceBaseAddress + "api/location?cityName=dc").Result;
if (response.IsSuccessStatusCode)
{
string r = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(r);
}
}
}
And the service is secured like this:
private void ConfigureAuth(IAppBuilder app)
{
var azureADBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
}
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureADBearerAuthOptions);
}
In my B2C tenant I have two different applications that are pretty much setup as this:
Both applications have been setup with secrets coming from the "keys" option. The keys generated are slightly differently structured than when using Azure Active Directory.
I can successfully get a token, but I get 401 when trying to connect to the other service. Do I have to do something different on the authorization side when using B2C compared to Azure Active Directory?
Azure Active Directory B2C can issue access tokens for access by a web or native app to an API app if:
Both of these apps are registered with B2C; and
The access token is issued as result of an interactive user flow (i.e. the authorization code or implicit flows).
Currently, your specific scenario -- where you are needing an access token to be issued for access by a daemon or server app to the API app (i.e. the client credentials flow) -- isn't supported, however you can register both of these apps through the “App Registrations” blade for the B2C tenant.
You can upvote support for the client credentials flow by B2C at:
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/18529918-aadb2c-support-oauth-2-0-client-credential-flow
If the API app is to receive tokens from both the web/native app as well as the daemon/server app, then you will have to configure the API app to validate tokens from two token issuers: one being B2C and other being the Azure AD directory in your B2C tenant.
I found the following very clear article from Microsoft which explains how to set up a "service account" / application which has management access to a B2C tenant. For me, that was the use case for which I wanted to "Authenticating as a Service with Azure AD B2C".
It is possible that having management access to a B2C tenant doesn't allow you access a protected resource for which your B2C tenant is the Authorization server (I haven't tried that), so the OP's use case may be slightly different but it feels very close.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
For automated, continuous tasks, you should use some type of service account that you provide with the necessary privileges to perform management tasks. In Azure AD, you can do this by registering an application and authenticating to Azure AD. This is done by using an Application ID that uses the OAuth 2.0 client credentials grant. In this case, the application acts as itself, not as a user, to call the Graph API.
In this article, we'll discuss how to perform the automated-use case. To demonstrate, we'll build a .NET 4.5 B2CGraphClient that performs user create, read, update, and delete (CRUD) operations. The client will have a Windows command-line interface (CLI) that allows you to invoke various methods. However, the code is written to behave in a noninteractive, automated fashion.

authority_not_in_valid_list: 'authority' is not in the list of valid addresses

I am trying to call a Authenticated API from my client app. However, when making AcquireTokenAsync, I get following error "authority_not_in_valid_list: 'authority' is not in the list of valid addresses"
here is my code snippet:
resourceUrl = "https://myApiEndPoint.com";
var clientCredential =
new ClientCredential( myClientAppId, myClientSecretKey );
// myClientAppId and myClientSecretKey are the values from Azure Portal
var authContext =
new AuthenticationContext( "https://my_authority/myApiEndPoint");
return await authContext.AcquireTokenAsync( resourceUrl, clientCredential );
In my azure Portal for my client Id of app, I have granted delegated permission to access https://myApiEndPOint.com api.
Any thoughts on what could be causing this issue and what does it mean by not in valid list?
I understand that:
you created your application in the Azure portal, and therefore the authority is the Azure AD endpoint. Therefore the authority is probably https://login.microsoftonline.com/common? Or do you have good reasons to use "https://my_authority" ?
you have granted delegated permissions to access the API. This means that your application will access the API in the name of the user. However the AcquireTokenAsync method that you use is using the "ClientCredential" flow (meaning with an application secret)
You probably rather want to use another override passing the resourceUri, the clientId, ...
If this is your use case, I suggest you have a look to the active-directory-dotnet-webapi-onbehalfof sample (See here)

Simple Directory Lookup in Azure Active Directory

I am writing a simple desktop application that needs to retrieve some basic properties about a user from Microsoft’ directory. Specifically:
I am writing a single tenant native LOB application.
The application runs on my desktop.
The application runs as my logged on domain account.
The organization' domain accounts are synced to AAD.
I am not trying to secure a native web app or a Web API or anything like that. I do not need users to sign in.
I have email addresses of folks in my organization from an external event management tool. I need to lookup the AAD account profile data (address book info - specifically job title) from AAD based on the email address. I will only be reading AAD data.
So far, I have done the following:-
It appears that the Azure AD Graph API is the right way to fetch the profile information. In particular, the information is available at the endpoint: https://graph.windows.net/{tenant}/users/{email}?api-version=1.6
When registering the native application in AAD, no key was provided. So I don't have a client secret.
Looked at the sample in GitHub here: https://github.com/Azure-Samples/active-directory-dotnet-graphapi-console. The instructions here seem to be wrong because no Keys section is available [see (2)].
Based on the sample above, I wrote a simple function. Code is below:
private static async Task PrintAADUserData(string email)
{
string clientId = "0a202b2c-6220-438d-9501-036d4e05037f";
Uri redirectUri = new Uri("http://localhost:4000");
string resource = "https://graph.windows.net/{tenant}";
string authority = "https://login.microsoftonline.com/{tenant}/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(resource, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
string api = String.Format("https://graph.windows.net/{tenant}/users/{0}?api-version=1.6", email);
LOG.DebugFormat("Using API URL {0}", api);
// Create an HTTP client and add the token to the Authorization header
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authResult.AccessTokenType, authResult.AccessToken);
HttpResponseMessage response = await httpClient.GetAsync(api);
string data = await response.Content.ReadAsStringAsync();
LOG.Debug(data);
}
Questions
The application when run was able to bring up the authentication page. Why do I need that? The application already runs as my domain account. Is an additional authentication necessary? If I were to run this application in Azure as a worker process, then I would not want to use my domain credentials.
The primary problem seems to be the resource URL which is wrong. What resource do I need to specify to access the Azure AD Graph API?
Thanks,
Vijai.
EDITS
Based on the comments from #Saca, the code and application has been edited.
Code
string clientId = ConfigurationManager.AppSettings["AADClientId"];
string clientSecret = ConfigurationManager.AppSettings["AADClientSecret"];
string appIdUri = ConfigurationManager.AppSettings["AADAppIdURI"];
string authEndpoint = ConfigurationManager.AppSettings["AADGraphAuthority"];
string graphEndpoint = ConfigurationManager.AppSettings["AADGraphEndpoint"];
AuthenticationContext authContext = new AuthenticationContext(authEndpoint, false);
AuthenticationResult authResult = await authContext.AcquireTokenAsync("https://graph.windows.net", new ClientCredential(clientId, clientSecret));
ExistingTokenWrapper wrapper = new ExistingTokenWrapper(authResult.AccessToken);
ActiveDirectoryClient client = new ActiveDirectoryClient(new Uri(graphEndpoint), async () => await wrapper.GetToken());
IUser user = client.Users.Where(_ => _.UserPrincipalName.Equals(email.ToLowerInvariant())).Take(1).ExecuteSingleAsync().Result;
App
Error
Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> Microsoft.Data.OData.ODataErrorException: Insufficient privileges to complete the operation. ---> System.Data.Services.Client.DataServiceQueryException: An error occurred while processing this request. ---> System.Data.Services.Client.DataServiceClientException: {"odata.error":{"code":"Authorization_RequestDenied","message":{"lang":"en","value":"Insufficient privileges to complete the operation."}}}
It appears that despite giving the right permissions, the correct resource and being able to acquire a token, there is still something missing.
The key thing to consider here is if your application will be a headless client run from a secure server or desktop client run by users on their machines.
If the former, then your application is considered a confidential client and can be trusted with secrets, i.e. the keys. If this is your scenario, which is the scenario covered by the sample, then you need to use clientId and clientSecret.
The most likely reason you are not seeing a Keys section in the your application's Configure page is that, instead of selecting Web Application and/or Web API as per step #7 in the sample, you selected Native Client Application when first creating the application. This "type" can't be changed, so you'll need to create a new application.
If your scenario is the latter, then your application is considered a public client and can't be trusted with secrets, in which case, your only options is to prompt the user for credentials. Otherwise, even if your app has it's own authorization layer, it can easily be decompiled and the secret extracted and used.
Your resource URL is correct by the way.
Turns out the real issue was not with the code. I am not an AAD administrator. It appears that any application needing to perform authentication against AAD in our tenant needs to have permissions enabled by the AAD administrators. Once they enabled permissions for my application (and took ownership of the AAD registration as well), this started working.
Hope help some one that are using GraphClient:
var userPriNam = "johndoe#cloudalloc.com";
var userLookupTask = activeDirectoryClient.Users.Where(
user => user.UserPrincipalName.Equals(userPriNam, StringComparison.CurrentCultureIgnoreCase)).ExecuteSingleAsync();
User userJohnDoe = (User)await userLookupTask;
from https://www.simple-talk.com/cloud/security-and-compliance/azure-active-directory-part-5-graph-api/

Resources