I have a .Net Core 3.1 API hosted in Azure App Service and this API uses the Azure AD to authenticate the users. I have requirement to show the list of users that have accessed this API.
app insights always show Zero users.
Things I have tried
Enabled the AppInsigts of web app
Enabled the telemetry in StartUp.cs
services.AddApplicationInsightsTelemetry(options => Configuration.Bind("ApplicationInsights", options))
Created the telemetry class to pass the User context
public void Initialize(ITelemetry telemetry)
{
var ctx = _httpContextAccessor.HttpContext;
if (ctx != null)
{
if (!string.IsNullOrEmpty(ctx.User.Identity.Name) &&
(string.IsNullOrEmpty(telemetry.Context.User.Id) || string.IsNullOrEmpty(telemetry.Context.Session.Id)))
{
telemetry.Context.User.Id = ctx.User.Identity.Name;
telemetry.Context.User.AuthenticatedUserId = ctx.User.Identity.Name;
telemetry.Context.Session.Id = ctx.User.Identity.Name;
}
}
}
Still I don't see any user in Azure portal
Related
I have a xamarin.forms mobile App using Microsoft.WindowsAzure.MobileServices and Microsoft.Identity.Client. Using EasyAuth I successfully got the xamarin mobile app to post data to the AzureSQL tables linked via connection string in the App Service configuration section. I use the local and offline sync methods of MobileServiceClient. I then attempted to change to B2C authentication. I setup a Tenant and under this tenant registered a new App as a native client called "MobileB2C". Redirect URIs were added automatically. I then created the signinsignup UserFlows.
Back to the Azure App Service (Mobile) under Authentication section I added a provider and selected the B2C App, MobileB2C. I did not populate the "allowed token audiences" field and Azure automatically created Client secret setting name "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET" and the issuer URL.
So when I run the xamarin mobile app I can login via azure B2C and I can see that the authResult returns the users correct info along with UserIdentifier,aud, iss, sub, oid etc.
Once authResult is returned the xamarin mobile then tries to use the sync methods of MobileServiceClient to save data to the AzureSQL table. Its at this point that it fails. When the line await mClient.SyncContext.PushAsync().ConfigureAwait(false); is hit an error occurs described as Microsoft.WindowsAzure.MobileServices.Sync.MobileServicePushStatus.CancelledByAuthentication. I continued to try and confirgure the Azure back end differently and now I no linger get the CancelledByAuthentication error but instead get Microsoft.WindowsAzure.MobileServices.Sync.MobileServicePushStatus.CancelledByNetworkError.
The relevant xamarin mobile app code to implement the authentication and AzureSQL table update is as follows;
private B2CAuthenticationService()
{
// default redirectURI; each platform specific project will have to override it with its own
var builder = PublicClientApplicationBuilder.Create(B2CConstants.ClientID)
.WithB2CAuthority(B2CConstants.AuthoritySignInSignUp)
.WithIosKeychainSecurityGroup(B2CConstants.IOSKeyChainGroup)
.WithRedirectUri($"msal{B2CConstants.ClientID}://auth");
// Android implementation is based on https://github.com/jamesmontemagno/CurrentActivityPlugin
// iOS implementation would require to expose the current ViewControler - not currently implemented as it is not required
// UWP does not require this
var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();
if (windowLocatorService != null)
{
builder = builder.WithParentActivityOrWindow(() => windowLocatorService?.GetCurrentParentWindow());
}
_pca = builder.Build();
}
public async Task<UserContext> SignInAsync()
{
UserContext newContext;
try
{
// acquire token silent
newContext = await AcquireTokenSilent();
}
catch (MsalUiRequiredException)
{
// acquire token interactive
newContext = await SignInInteractively();
}
return newContext;
}
private async Task<UserContext> SignInInteractively()
{
AuthenticationResult authResult = await _pca.AcquireTokenInteractive(B2CConstants.Scopes)
.ExecuteAsync();
var newContext = UpdateUserInfo(authResult);
UserSingleton.Instance.UserId = newContext.UserIdentifier;
return newContext;
}
THe xamarin mobile app adds a record to the local database and then RefreshItemsAsync begins the synchronisation to the AzureSQL.
await azureService.AddUserSurveyAsync(newSurvey).ConfigureAwait(false);
await azureService.RefreshItemsAsync(true).ConfigureAwait(false);
It is at the PushAsync line below that the the code fails.
public async Task InitializeAsync()
{
using (await initializationLock.LockAsync())
{
if (!isInitialized)
{
mClient = new MobileServiceClient(https://mobileservice.azurewebsites.net);
// Define the offline store.
mStore = new MobileServiceSQLiteStore("mobile3.db");
mStore.DefineTable<UserSurvey>();
await mClient.SyncContext.InitializeAsync(mStore, new MobileServiceSyncHandler()).ConfigureAwait(false);
UserSurveyTable = mClient.GetSyncTable<UserSurvey>();
isInitialized = true;
}
}
}
public async Task RefreshItemsAsync(bool syncItems)
{
if (syncItems)
{
await SynchronizeAsync().ConfigureAwait(false);
}
}
public async Task SynchronizeAsync()
{
await InitializeAsync().ConfigureAwait(false);
IReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
if (!CrossConnectivity.Current.IsConnected)
return;
try
{
await mClient.SyncContext.PushAsync().ConfigureAwait(false);
await UserSurveyTable.PullAsync("usersurveys", UserSurveyTable.CreateQuery()).ConfigureAwait(false);
}
catch (MobileServicePushFailedException error)
{
if (error.PushResult != null)
{
foreach (var result in error.PushResult.Errors)
{
await ResolveError(result);
}
}
}
}
What is wrong with the Azure back end configuration or perhaps I'm missing code as I can't understand how the xamarin mobile app can then attempt to communicate with the Azure Mobile App Service and AzureSQL as I don't send any token with those lines of code for PushAsync etc or perhaps this is abstracted away?
Here are images of the exceptions;
enter image description here
enter image description here
As promised, here is the succinct version of AAD authentication. For your purposes, B2C authentication is the same as AAD authentication.
There are two application definitions at play here - one for the mobile application (which basically says "this person is authenticated"), and one for the service (which says "a token authenticated for this mobile application can access this service"). So, you create an application ID for your mobile application, and an application ID for your service, and then you configure the service application ID to accept the mobile application.
The "WPF" tutorial for Azure Mobile Apps gives the general overview, although it's for WPF instead of Xamarin. The pieces you need are all the same.
The "WPF" tutorial here: https://learn.microsoft.com/en-us/azure/developer/mobile-apps/azure-mobile-apps/quickstarts/wpf/authentication
We have the following:-
SharePoint online tenant recently created
Windows server 2019
.NET console application which have some code that integrates with SharePoint online
The .NET console application runs on schedule basis using windows task scheduler.
now previously on old tenants i authenticate my code using this method by passing the ClientID and Client Secret:-
static void Main(string[] args)
{
string siteUrl = "https://***.sharepoint.com/sites/CustomerServiceKB/";
string clientId = "******";
string clientSecret = "*****";
using (ClientContext context = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext(siteUrl, clientId, clientSecret))
{
but on our newly created tenant we can not authenticate our code using the above method, because we have the DisableCustomAppAuthentication set to true.. now we do not want to modify this property.
So our question is; if we have the DisableCustomAppAuthentication set to true (ans we do not want to set it to false), then how we can authenticate our console application? which is hosted inside our windows server and which runs on schedule basis using tasks scheduler ?
So the DisableCustomAppAuthentication property was brought in (and was set to true by default) to support the deprecation of the Azure Access Control Service (ACS). The modern way to authenticate custom apps in Sharepoint tenants is to register them in the Azure AD.
Before moving on, consider the pros and cons of switching to the new authentication scheme. Mainly, the ACS enables users to granularly control site-level permissions for the application authentication, but Azure AD app registration makes the set of running applications transparent to the administrators. If you want to stay with ACS, just set the DisableCustomAppAuthentication to false.
Now, if you decided to move on with registering the application in the Azure ID, here are the steps to follow:
Log in to the Azure portal and navigate to the Azure Active Directory.
Register the application in the Azure AD portal. Here's a guide on how to do it. Obtain the application (client) ID on the application overview page.
Set all the necessary permissions, confirm them as a global administrator (or ask the administrator for confirmation).
Configure the authentication. Choose the authentication option: whether you want the application to authenticate itself in the Microsoft IAM via a cryptographic certificate or a client secret.
Obtain your Azure Active Directory tenant ID (not to be confused with the Sharepoint Online tenant ID). Here's how to do in in the Azure AD; there's also a hacky way.
Now use the client ID (from step 2), the authentication option (step 4), and the AAD tenant ID (step 5) to authenticate and run your application:
using Microsoft.Identity.Client;
[..]
private static async Task<string> GetToken()
{
string applicationId = "client-id";
string tenantId = "aad-tenant-id";
bool isUsingClientSecret = <true or false>;
IConfidentialClientApplication app;
if (isUsingClientSecret)
{
string secret = "secret";
app = ConfidentialClientApplicationBuilder.Create(applicationId)
.WithClientSecret(secret)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.Build();
}
else
{
string certificateLocation = "certificate-file";
X509Certificate2 certificate = ReadCertificate(certificateLocation);
app = ConfidentialClientApplicationBuilder.Create(applicationId)
.WithCertificate(certificate)
.WithAuthority($"https://login.microsoftonline.com/{tenantId}")
.Build();
}
var scopes = new[] { "https://***.sharepoint.com/.default" };
var authenticationResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
return authenticationResult.AccessToken;
}
static async Task MainAsync(string[] args)
{
string site = "https://***.sharepoint.com/sites/CustomerServiceKB";
string token = await GetToken();
using (ClientContext context = new ClientContext(site))
{
context.ExecutingWebRequest += (s, e) =>
{
e.WebRequestExecutor.RequestHeaders["Authorization"] = "Bearer " + token;
};
Web web = context.Web;
context.Load(web);
context.ExecuteQuery();
}
}
static void Main(string[] args)
{
try
{
AsyncContext.Run(() => MainAsync(args));
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
throw;
}
}
I have an Azure Function (v3) that must interact with DB and also with user management.
It has as a dependency a project that also contains the DAL so all the context configuration.
When in the Startup of the function I add the dependency to the DbContext and deploy it on Azure I have no problems.
When in addition to the DbContext I also add Identity and re-deploy, the Portal says "Azure Functions runtime is unreachable".
This is the function Startup.cs:
[assembly: FunctionsStartup(typeof(Directio.PeopleSee.OrderSynchronizer.Startup))]
namespace Directio.PeopleSee.OrderSynchronizer
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddDbContext<DBContext>(options1 =>
{
options1.UseLazyLoadingProxies(false);
options1.UseSqlServer(Environment.GetEnvironmentVariable("DBConnectionString"), builder =>
{
builder.CommandTimeout(10);
}
);
})
.AddIdentity<AspNetUser, AspNetRole>(opt =>
{
opt.Password.RequireDigit = false;
opt.Password.RequireLowercase = false;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = false;
opt.Password.RequiredLength = 0;
opt.Password.RequiredUniqueChars = 0;
opt.User.RequireUniqueEmail = false;
opt.SignIn.RequireConfirmedEmail = false;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<DBContext>()
.AddDefaultTokenProviders();
builder.Services.AddOptions<FunctionAppSettings>().Configure<IConfiguration>((settings, configuration) => configuration.GetSection("FunctionAppSettings").Bind(settings));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IUserService, UserServiceImpl>();
builder.Services.AddScoped<IRoleService, RoleServiceImpl>();
builder.Services.AddScoped<ISubscribersService, SubscriberServiceImpl>();
builder.Services.AddScoped<IOrdersService, OrdersService>();
}
}
}
All services registrations come from the project the dependency is linked to.
The function is under netcore3.1 as a framework, and is deployed in an Azure Func App with a pay-as-you-go plan, Windows server and it's not a docker container.
What could be the problem?
When you add Identity you are also adding the AspNetCore Authentication dependencies. For some reason Functions host checks if these dependencies exist in your applications and stops the Host module from adding the built-in authentication schemes that allows the portal to have access to the functions in your project.
If you still want to load Identity components at startup time I encourage you to look at https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
Here is a post describing how it works: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3
you can call
builder.AddAuthentication();
builder.AddAuthorization();
// or
builder.Services.AddFunctionsAuthentication();
builder.Services.AddFunctionsAuthorization();
This call forces the functions built-in authentication to be loaded and allows you to add more authentication dependencies and schemes.
After this call you can make your calls AddDbContext<... and AddIdentity<...
Azure portal should be able to get all the information it needs about your functions.
I created a simple ASP.NET Core Web application using OAuth authentication from Google. I have this running on my local machine fine.
Yet after deploying this as an AppService to Azure the OAuth redirects seem to get messed up.
The app itself can be found here:
https://gcalworkshiftui20180322114905.azurewebsites.net/
Here's an url that actually returns a result and shows that the app is running:
https://gcalworkshiftui20180322114905.azurewebsites.net/Account/Login?ReturnUrl=%2F
Sometimes the app responds fine but once I try to login using Google it keeps loading forever and eventually comes back with the following message:
The specified CGI application encountered an error and the server terminated the process.
Behind the scenes, the authentication callback that seems to be failing with a 502.3 error:
502.3 Bad Gateway “The operation timed out”
The error trace can be found here:
https://gcalworkshiftui20180322114905.azurewebsites.net/errorlog.xml
The documentation from Microsoft hasn't really helped yet.
https://learn.microsoft.com/en-us/azure/app-service/app-service-authentication-overview
Further investigation leads me to believe that this has to do with the following code:
public GCalService(string clientId, string secret)
{
string credPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials/calendar-dotnet-quickstart.json");
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets
{
ClientId = clientId,
ClientSecret = secret
},
new[] {CalendarService.Scope.Calendar},
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
// Create Google Calendar API service.
_service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "gcalworkshift"
});
}
As I can imagine Azure not supporting personal folders? Googling about this doesn't tell me much.
I followed Facebook, Google, and external provider authentication in ASP.NET Core and Google external login setup in ASP.NET Core to create a ASP.NET Core Web Application with Google authentication to check this issue.
I also followed .NET console application to access the Google Calendar API and Calendar.ASP.NET.MVC5 to build my sample project. Here is the core code, you could refer to them:
Startup.cs
public class Startup
{
public readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder); //C:\Users\{username}\AppData\Roaming\Google.Apis.Auth
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = "{ClientId}";
googleOptions.ClientSecret = "{ClientSecret}";
googleOptions.Scope.Add(CalendarService.Scope.CalendarReadonly); //"https://www.googleapis.com/auth/calendar.readonly"
googleOptions.AccessType = "offline"; //request a refresh_token
googleOptions.Events = new OAuthEvents()
{
OnCreatingTicket = async (context) =>
{
var userEmail = context.Identity.FindFirst(ClaimTypes.Email).Value;
var tokenResponse = new TokenResponse()
{
AccessToken = context.AccessToken,
RefreshToken = context.RefreshToken,
ExpiresInSeconds = (long)context.ExpiresIn.Value.TotalSeconds,
IssuedUtc = DateTime.UtcNow
};
await dataStore.StoreAsync(userEmail, tokenResponse);
}
};
});
services.AddMvc();
}
}
}
CalendarController.cs
[Authorize]
public class CalendarController : Controller
{
private readonly IDataStore dataStore = new FileDataStore(GoogleWebAuthorizationBroker.Folder);
private async Task<UserCredential> GetCredentialForApiAsync()
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{ClientId}",
ClientSecret = "{ClientSecret}",
},
Scopes = new[] {
"openid",
"email",
CalendarService.Scope.CalendarReadonly
}
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
string userEmail = ((ClaimsIdentity)HttpContext.User.Identity).FindFirst(ClaimTypes.Name).Value;
var token = await dataStore.GetAsync<TokenResponse>(userEmail);
return new UserCredential(flow, userEmail, token);
}
// GET: /Calendar/ListCalendars
public async Task<ActionResult> ListCalendars()
{
const int MaxEventsPerCalendar = 20;
const int MaxEventsOverall = 50;
var credential = await GetCredentialForApiAsync();
var initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "ASP.NET Core Google Calendar Sample",
};
var service = new CalendarService(initializer);
// Fetch the list of calendars.
var calendars = await service.CalendarList.List().ExecuteAsync();
return Json(calendars.Items);
}
}
Before deploying to Azure web app, I changed the folder parameter for constructing the FileDataStore to D:\home, but got the following error:
UnauthorizedAccessException: Access to the path 'D:\home\Google.Apis.Auth.OAuth2.Responses.TokenResponse-{user-identifier}' is denied.
Then, I tried to set the parameter folder to D:\home\site and redeploy my web application and found it could work as expected and the logged user crendentials would be saved under the D:\home\site of your azure web app server.
Azure Web Apps run in a secure environment called the sandbox which has some limitations, details you could follow Azure Web App sandbox.
Additionally, you mentioned about the App Service Authentication which provides build-in authentication without adding any code in your code. Since you have wrote the code in your web application for authentication, you do not need to set up the App Service Authentication.
For using App Service Authentication, you could follow here for configuration, then your NetCore backend can obtain additional user details (access_token,refresh_token,etc.) through an HTTP GET on the /.auth/me endpoint, details you could follow this similar issue. After retrieved the token response for the logged user, you could manually construct the UserCredential, then build the CalendarService.
The examples I've seen for using Azure B2C with Web API show app.UseOAuthBearerAuthentication (as shown below), however my ASP .NET 5 Web API project uses IApplicationBuilder (not IAppBuilder) and UseOAuthBearerAuthentication does not exist. I've tried app.UseOpenIdConnectAuthentication, however I believe this uses cookies and I couldn't get it working using a Xamarin app as a client. I've tried app.UseWindowsAzureActiveDirectoryBearerAuthentication but I believe this is for standard Azure AD (not B2C) is that true? Any ideas how to get Azure B2C working with the very latest ASP .NET Web API?
Thanks!!!
public void ConfigureAuth(IAppBuilder app)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = clientId,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, "v2.0", discoverySuffix, commonPolicy)))
});
}
This works for me. I hope it helps someone else who is looking to use Azure B2C with the latest .NET Web API framework:
public void ConfigureAuth(IApplicationBuilder app, IOptions<PolicySettings> policySettings)
{
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme,
AutomaticAuthenticate = true,
AutomaticChallenge = true,
MetadataAddress = "https://login.microsoftonline.com/[my-tenant].onmicrosoft.com/v2.0/.well-known/openid-configuration?p=B2C_1_my-signup-signin-policy",
Audience = "[My-Azure-App-Guid]",
Events = new JwtBearerEvents
{
OnTokenValidated= ctx =>
{
var nameClaim = ctx.AuthenticationTicket.Principal.FindFirst("name");
if (nameClaim != null)
{
var claimsIdentity = (System.Security.Claims.ClaimsIdentity)ctx.AuthenticationTicket.Principal.Identity;
claimsIdentity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, nameClaim.Value));
}
return Task.FromResult(0);
},
OnAuthenticationFailed = ctx =>
{
ctx.SkipToNextMiddleware();
return Task.FromResult(0);
}
}
});
}
If the JWT token is available in your client, then you can use JwtBearerAuthentication in the Web API. The JWT issued by Azure B2C ( social logins) can be used as a token to get authenticated in the Web API.
Refer to https://github.com/sendhilkumarg/AzureB2CWebAppAndAPIAuthentication
In this sample the client is a web app