I have trouble to sign in to my Azure B2C Active Directory with username and password from .net core console app. I am using the MSAL Library 2.6 from nuget.
I found this example on GitHub https://github.com/Azure-Samples/active-directory-dotnetcore-console-up-v2
I reduced the code to a minimum it looks like this:
public static async Task TestLogin()
{
try
{
var scopes = new[] { "community.member" };
var username = "{user#domain.com}";
var password = new SecureString();
foreach (var c in "{Password}")
{
password.AppendChar(c);
}
var app = new PublicClientApplication("{B2C Application ID}", "https://{Application Name}.b2clogin.com/tfp/xxxxxxx-xxxx-xxxxxxxxxxxx/B2C_1_NativeSigneIn");
var result = await app.AcquireTokenByUsernamePasswordAsync(scopes, username, password);
Console.WriteLine($"Token: {result.AccessToken}");
}
catch (MsalServiceException ex)
{
Console.WriteLine($"{ex.Message} - {ex.ErrorCode}");
}
}
I am always getting the following MSAL exception:
Message: User realm discovery failed
ErrorCode: user_realm_discovery_failed
The old library has a bug in it. The new version of MSAL has the fix. You can read about the issue and the resolution here:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/926
Basically you need to download the local version of the package that one of the developers compiled from the GITHUB source, reference that package from your solution by pointing NuGet Package Manager to the downloaded package, then you need to rework the API for the new Fluent API for MSAL, and it'll work! Easy as that.
I tried the B2C policy, the ropc flow cannot support other policies, you could only use the ropc policy.
For the details, you could read here.
Related
I am developing an application which is based on GCP (Specifically it runs on the Google Cloud Composer (a managed version of Airflow)). From there I would like to connect to the Microsoft Graph API of another organization. So I want the application to be running in the background and every x minutes retrieve new emails from the inbox of an external organization, then do some processing and based on that perform some actions in said mailbox through the Graph API.
I am trying to figure out the best practice on how to secure this connection. I believe I could use the client secret of the registered application and then store that in an azure keyvault. Now I am not sure how I should authenticate my registered app such that it can retrieve this client secret which in turn can be used to access the Graph API?
As you can probably tell I'm not quite sure whether this makes sense or what a better approach would be? Almost all the information I am finding is concerning managed identities, however if I understood correctly these can only be used when the application is running natively on Azure, which for me is not the case.
Would really appreciate any help!
If I don't misunderstand, you can refer to my sample code, pls note you need to add key vault access policy first. And you may refer to this doc to know about the default azure credential.
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace test0430callingapi.Controllers
{
public class HelloController : Controller
{
public async Task<string> IndexAsync()
{
const string secretName = "clientsecret";
var kvUri = "https://key_vault_name.vault.azure.net/";
var a = new DefaultAzureCredential();
var client = new SecretClient(new Uri(kvUri), a);
var secret = await client.GetSecretAsync(secretName);
string secretVaule = secret.Value.Value;
//azure ad client credential flow to generate access token
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create("azure_ad_app_clientid")
.WithClientSecret(secretVaule)
.WithAuthority(new Uri("https://login.microsoftonline.com/your_tanent_name.onmicrosoft.com"))
.Build();
AuthenticationResult result = null;
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
string accesstoken = result.AccessToken;
// this token can be used to call https://graph.microsoft.com/v1.0/users/user_id/mailFolders/{id}/messages
return accesstoken;
}
}
}
I used client credential flow here which don't need to make users sign in, and this api support application permission. If you use an api which only supports delegated permission, it can't work. Here's the api calling response.
I'm running ASP.NET Core application (.Net Core 3.0) and have referenced nuGet package Microsoft.Azure.Management.WebSites. It seems like there are half a dozen ways to connect to Azure and I'm hoping that is the correct one for my environment.
I'm attempting to instantiate a WebSiteManagementClient so that I can modify some AppService settings (scale service plan up/down). To that end, I need an instance of ServiceClientCredentials. I can't seem to find a way to get the proper credentials together.
I've followed several different articles, all of them advocate a different method.
What's the easiest way to get authenticated against the Azure Management SDK?
Ideally, avoiding Azure Active Directory. I've attempted multiple times trying to set up an App Registration with the appropriate permissions, and I can't seem to get it together.
The app connecting and making the change will be an ASP.NET website running in Azure itself, if it makes a difference.
Thanks in advance!
Code so far:
using Microsoft.Azure.Management.WebSites;
var credentials = await GetCredentials(); // <-- ???
WebSiteManagementClient client = new WebSiteManagementClient(credentials);
client.SubscriptionId = "xxx-xxx-xxxx-xxxx";
Try this :
static void Main(string[] args)
{
string tenantId = "<your tenant ID>";
string clientId = "<your azure ad app ID>";
string clientSecret = "<azure ad app secret>";
string subscriptionId = "<your subscription ID>";
WebSiteManagementClient client = new WebSiteManagementClient(GetCredsFromServicePrincipal(tenantId, clientId, clientSecret));
client.SubscriptionId = subscriptionId;
foreach (var ap in client.app.List()) {
Console.WriteLine(ap.Id);
}
}
private static TokenCredentials GetCredsFromServicePrincipal(String tenantId,String clientId, String clientSecret)
{
var authority = #"https://login.microsoftonline.com/" + tenantId;
var authContext = new AuthenticationContext(authority);
var credential = new ClientCredential(clientId, clientSecret);
var authResult = authContext.AcquireTokenAsync("https://management.azure.com", credential).GetAwaiter().GetResult();
return new TokenCredentials(authResult.AccessToken);
}
Result (list all website ids):
As this sample use ServicePrincipal to access your azure website resources, so you should grant associated permissions it in your subscription "Access control (IAM)" balde, such as assigning "website contributor" and "web plan contributor" to it so it has permission to manage your website resources . Hope it helps.
The new Azure.Identity library seems to be the recommended way for authenticating services within Azure. In particular the DefaultAzureCredentials() class works seamlessly in local development scenarios and in deployed code without having to make any code changes.
This is easy to use with the newer management SDKs (the ones with names like Azure.ResourceManager...) because we can just write new DefaultAzureCredentials() and pass that to the management SDK when creating a new client.
Unfortunately, the older management SDKs (the ones with names like Microsoft.Azure.Management...) do not integrate with Azure.Identity "out-of-the-box". They also do not plan to add support for Azure.Identity to these older APIs because they are instead focusing on porting everything to the newer versions.
However, not every resource in Azure has a new version management API yet and so in some cases you're stuck using the old ones. Fortunately, there is a relatively straight forward way to bridge the gap and still use Azure.Identity with those older APIs.
There's a GitHub repo which contains an example of how to achieve this. I think it's by one of the developers on the Microsoft team, but isn't officially supported by Microsoft. There is no NuGet package for it and they recommend just copying the bits you need.
I actually found that the code in that sample repo was overly complex for my needs and in my case that all I needed was this. Note, I've copied this from my F# project without testing it, so I might have made a mistake in the conversion to C#, but hopefully it's close enough that you get the idea.
class AzureIdentityFluentCredentialAdapter : AzureCredentials
{
public AzureIdentityFluentCredentialAdapter(string tenantId)
: base(default(DeviceCredentialInformation), tenantId, AzureEnvironment.AzureGlobalCloud)
{
}
public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var creds = DefaultAzureCredential() // Use the new Azure.Identity library to get access tokens
var accessToken = await creds.GetTokenAsync(
new TokenRequestContent(new [] { "https://management.azure.com/.default" }),
cancellationToken);
return await TokenCredentials(accessToken.Token)
.ProcessHttpRequestAsync(request, cancellationToken);
}
}
This example doesn't do any token caching, but for my purposes I wasn't too bothered about this. It's also hardcoded the scope that I request the token for because I knew I was only going to be using this with the Azure management API.
I want to call this API from azure resource manager to get the storage keys:
https://learn.microsoft.com/en-us/rest/api/storagerp/storageaccounts/listkeys
I want to use user authentication for this call and possibly .net sdk.
Is there any .net sdk(Nuget package) I can include in my c# project to call this API? I am seeing many solution which is using .net sdk but they are using AAD app secret, but I cannot use secret in the app since it is a desktop app. I think there should be a way to call these API with user auth and .net sdk.
The Microsoft.Azure.Services.AppAuthentication for .NET library may meet your requirement. It uses the developer's credentials to authenticate during local development. When the solution is later deployed to Azure, the library automatically switches to application credentials.
The Microsoft.Azure.Services.AppAuthentication library supports local development with Microsoft Visual Studio, Azure CLI, or Azure AD Integrated Authentication.
Sample:
using Microsoft.Azure.Management.Storage;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Rest;
namespace ConsoleApp6
{
class Program
{
static void Main(string[] args)
{
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = azureServiceTokenProvider.GetAccessTokenAsync("https://management.azure.com/").Result;
var Credentials = new TokenCredentials(accessToken);
var storageManagementClient = new StorageManagementClient(Credentials);
storageManagementClient.SubscriptionId = "subscription id";
var storageAccountKey = storageManagementClient.StorageAccounts.ListKeysAsync("resource grouop name", "storage account name");
string storage_Account_Key = storageAccountKey.Result.Keys[0].Value;
System.Console.WriteLine(storage_Account_Key);
}
}
}
For more details about the authentication, you could take a look at this link.
I'm new to Azure and struggle a little in learning all the functionalities of the Azure Active Directory (AAD), so I hope you can clear some things up for me. Here is what I already did:
I registered a web app which serves as a resource provider and offers different APIs behind a API management service.
The web app has several users and roles in the AAD. Plus, more detailed permissions are set on App-Level. So the AAD doesn't control all permissions of my users.
Users are authenticated by using OAuth 2.0. In practice, this means if a new user tries to login to my app he gets redirected to Microsofts login page, enters username and password and then gets a JWT token from Microsofts authentication server.
Now what I want to do:
I want to write an app running on my build server which tests the user permissions. The app has to be written in C# .NET Core. Now I'm struggling on how to log in as a user from my code, so my question is:
How can i log in as a user from code to AAD and get the JWT token to test the user permissions? Can I do this by just using username / password, or do I need to register my test app in the AAD? What are the best solutions to reach my goals?
Thank you in advance
Juunas' comment already covered most of what is required. Just putting a bit more detail behind it.
You can use MSAL (link) to write a .NET Core application that accesses your API.
Within MSAL, you need to use username password authentication (Resource Owner Password Credentials grant) to acquire a JWT token. Please never use this grant outside your testing application.
Depending on how your app is configured, using just the clientId of the API could be enough. It would however be best practice to register a separate native app.
Some wording to help you along:
ClientId: The id of the client application which is requesting the token.
Scope: The scope of the API you acquire the token for. Should already be configured somewhere in your API. Usually something with the AppId URI. Possible examples could look like:
https://<yourtenant>.onmicrosoft.com/<yourapi>/user_impersonation
https://<clientId-of-API>/.default
...
Authority: Your AAD, e.g. https://login.microsoftonline.com/yourtenant.onmicrosoft.com
Code example for the password grant from the wiki (more examples there):
static async Task GetATokenForGraph()
{
string authority = "https://login.microsoftonline.com/contoso.com";
string[] scopes = new string[] { "user.read" };
PublicClientApplication app = new PublicClientApplication(clientId, authority);
try
{
var securePassword = new SecureString();
foreach (char c in "dummy") // you should fetch the password
securePassword.AppendChar(c); // keystroke by keystroke
result = await app.AcquireTokenByUsernamePasswordAsync(scopes, "joe#contoso.com",
securePassword);
}
catch(MsalException)
{
// See details below
}
Console.WriteLine(result.Account.Username);
}
I actually find out a way to do it in "pure" C# without using the MSAL library, which I had some trouble with. So if you're looking for a solution w/o MSAL, you can do it the way described below.
Prerequisites
A user must exist in the AAD and must not use a Microsoft Account (source in Active Directory must not be "Microsoft Account").
A client application must be registered in the Azure Active Directory. The client app must be granted permissions to the app you want to test. If the client app is of type "Native", no client secret must be provided. If the client app is of type "Web app / api", a client secret must be provided. For testing purposes, its recommended to use an app of type "Native" without a client secret.
There must be no two factor authentication.
C# Code
You can than create a class "JwtFetcher" and use code like this:
public JwtFetcher(string tenantId, string clientId, string resource)
{
this.tenantId = !string.IsNullOrEmpty(tenantId) ? tenantId : throw new ArgumentNullException(nameof(tenantId));
this.clientId = !string.IsNullOrEmpty(clientId) ? clientId : throw new ArgumentNullException(nameof(clientId));
this.resource = !string.IsNullOrEmpty(resource) ? resource : throw new ArgumentNullException(nameof(resource));
}
public async Task<string> GetAccessTokenAsync(string username, string password)
{
var requestContent = this.GetRequestContent(username, password);
var client = new HttpClient
{
BaseAddress = new Uri(ApplicationConstant.Endpoint.BaseUrl)
};
var message = await client.PostAsync(this.tenantId + "/oauth2/token", requestContent).ConfigureAwait(false);
message.EnsureSuccessStatusCode();
var jsonResult = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
dynamic objectResult = JsonConvert.DeserializeObject(jsonResult);
return objectResult.access_token.Value;
}
private FormUrlEncodedContent GetRequestContent(string username, string password)
{
List<KeyValuePair<string, string>> requestParameters = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.GrantType, ApplicationConstant.RequestParameterValue.GrantTypePassword),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Username, username),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Password, password),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.ClientId, this.clientId),
new KeyValuePair<string, string>(ApplicationConstant.RequestParameterName.Resource, this.resource)
};
var httpContent = new FormUrlEncodedContent(requestParameters);
return httpContent;
}
The grant type for this is just "password".
I've almost configured my OpenId owin authentication/authorization in Azure Active Directory. My configuration is the following:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
CookieName = "AppServiceAuthSession"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = ClientId,
Authority = _authority,
PostLogoutRedirectUri = PostLogoutRedirectUri,
RedirectUri = PostLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
AuthorizationCodeReceived = async context =>
{
var id = new ClaimsIdentity(context.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(context.AuthenticationTicket.Identity.Claims);
var appToken = "MyToken";
id.AddClaim(new Claim("MyTokenKey", appToken));
context.AuthenticationTicket = new AuthenticationTicket
(
new ClaimsIdentity(id.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties
);
}
},
});
But I want to add one more application token (not user token) to claims list to be able to have ability to use this token in any place on my site. Also it's good point for me that I don't need to get this token from my external token provider more then one time per an authentication session.
But place, where I'm going to add my logic (AuthorizationCodeReceived as well as other methods from OpenIdConnectAuthenticationNotifications) is called only when I use my local IIS(run locally), when I try to use azure IIS, this method has not been called at all. In this case my User is authenticated anyway, but this method and the similar methods from OpenIdConnectAuthenticationNotifications(except RedirectToIdentityProvider) are not fired.
I've downloaded the git source code of Katana project and referenced this project to my instead of the official nuget packages to debug its and as I think currently, I've found the reason why it happens. The AuthorizationCodeReceived "event" method is called from OpenIdConnectAuthenticationHandler class in AuthenticateCoreAsync method. But also, the calling of this method is required that the below checking must give the true result:
if (string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match.
&& Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)
&& Request.Body.CanRead)
{
//some necessary preparation to call `AuthorizationCodeReceived` event method
}
As we can see, this checking allows only POST requests and I see these POST requests when I run app in local IIS, but I cannot see these POST requests when I deploy my application in azure portal (I've debugged both of options : on local IIS and in azure portal).
As summary from the above, this is the only one difference between these runnings. (Azure IIS doesn't send POST request at all by some reason).Any other methods in Katana project (which I checked) are called in the same way.
Could anybody help with it?
PS Note, I check any changes only after clearing of browser data (cache/history and so on).
The answer is the following:
The authorization in azure portal should be configured as shown above. In case if you chose LogIn with Azure Active Directory, then app services auth takes place outside of your app, and the custom authorization is not triggered.