Current Status:
I am trying to use impersonation in ASP.NET Core MVC 3.1 and windows domain\user credentials.
Unfortunately, the method below is impersonating using identity from the application pool.
Target:
I need to stream the audio using the identity of the domain\user
hitting the method.
The user will be prompted by the browser to login into the windows domain ONLY ONCE in the main page
Impersonation needs to work without requesting the password, ever again
Code
public Task<IActionResult> StreamAudioLive(string path)
{
WindowsIdentity.RunImpersonated(((WindowsIdentity)HttpContext.User.Identity).AccessToken, () =>
{
var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
return new FileStreamResult(fs, "audio/mp3")
{
EnableRangeProcessing = true,
};
});
return null;
}
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
My ASP.Net Core MVC app accesses a .Net Core API through IdentityServer. It works fine on IIS server running in-process with Entity Framework based identity store. Now I am trying to enable Windows Authentication and getting stuck here.
What I tried is following the identityserver doc section "Windows Authentication" - I added the code below to the ConfigureServices of my IdentityServer's Startup.cs
// configures IIS in-proc settings
services.Configure<IISServerOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
I also enabled the Windows Authentication in IIS for my API app
The part of the doc that I am confused about is "You trigger Windows authentication by calling ChallengeAsync on the Windows scheme". It doesn't mention where you do that. I am assuming it is in identityserver and I put the code in the Login method of the AccountController of the identityserver as bellow.
/// <summary>
/// Entry point into the login workflow
/// </summary>
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
// trigger Windows authentication by calling ChallengeAsync
await ChallengeWindowsAsync(returnUrl);
// build a model so we know what to show on the login page
var vm = await BuildLoginViewModelAsync(returnUrl);
if (vm.IsExternalLoginOnly)
{
// we only have one option for logging in and it's an external provider
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
}
return View(vm);
}
private async Task<IActionResult> ChallengeWindowsAsync(string returnUrl)
{
// see if windows auth has already been requested and succeeded
var result = await HttpContext.AuthenticateAsync("Windows");
if (result?.Principal is WindowsPrincipal wp)
{
// we will issue the external cookie and then redirect the
// user back to the external callback, in essence, treating windows
// auth the same as any other external authentication mechanism
var props = new AuthenticationProperties()
{
RedirectUri = Url.Action("Callback"),
Items =
{
{ "returnUrl", returnUrl },
{ "scheme", "Windows" },
}
};
var id = new ClaimsIdentity("Windows");
// the sid is a good sub value
id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.FindFirst(ClaimTypes.PrimarySid).Value));
// the account name is the closest we have to a display name
id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name));
// add the groups as claims -- be careful if the number of groups is too large
var wi = wp.Identity as WindowsIdentity;
// translate group SIDs to display names
var groups = wi.Groups.Translate(typeof(NTAccount));
var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
id.AddClaims(roles);
await HttpContext.SignInAsync(
IdentityServerConstants.ExternalCookieAuthenticationScheme,
new ClaimsPrincipal(id),
props);
return Redirect(props.RedirectUri);
}
else
{
// trigger windows auth
// since windows auth don't support the redirect uri,
// this URL is re-triggered when we call challenge
return Challenge("Windows");
}
}
What I expect to happen, if everything goes well, is that the authentication happens automatically (without a login box?) because the "Challenge" call will require the client side (the browser) to send in Windows identity info and a token will be issued based on that.
It doesn't seem to work that way now - I am getting an Unauthorized error from API when starting the MVC app:
Am I doing that in the wrong place? Or am I missing something else?
I am unable to signout from a .Net core Web App while using Azure AD to login.
When i try to login using a different email, its just logging in as the previous user who logged in. Its not even asking for password when the new user is trying to login.
I have tried the following approaches but -
// Send an OpenID Connect sign-out request.
HttpContext.GetOwinContext().Authentication.SignOut(
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
OR
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
or
if (HttpContext.User.Identity.IsAuthenticated)
{
await HttpContext.Authentication.SignOutAsync(settings.SignInPolicyId);
await HttpContext.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
None of these work on .Net Core.
This code below builds and runs on .net core but it doesnt completely logout the user.
Request.GetOwinContext().Authentication.SignOut(new AuthenticationProperties { RedirectUri = clsCommonFunction.GetLoginURL() });
Please help.
Here is the SignOut method for ASP.NET Core.
[HttpGet]
public IActionResult SignOut()
{
var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties { RedirectUri = callbackUrl },
CookieAuthenticationDefaults.AuthenticationScheme,
OpenIdConnectDefaults.AuthenticationScheme);
}
[HttpGet]
public IActionResult SignedOut()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToAction(nameof(HomeController.Index), "Home");
}
return View();
}
Its not even asking for password when the new user is trying to
login.
Please clear the browser cache Ctrl+F5.
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.
I'm trying to use a local login form to authenticate a user credentials against its external provider (Azure Active Directory).
I understand that, per client, you can enable local login. That helps, as when set to true, I'll get the local login form but but I'm still unclear as to how to fire off the middle ware for that external provider. Is there a way to send client credentials to the external provider to receive an ID token? My current code redirects to the Microsoft login; and then back to my identity server, and then the client application. I want the user to login in through identity server but not have them know it's really authenticating against Azure.
Here's my start up:
var schemeName = "Azure-AD";
var dataProtectionProvibder = app.ApplicationServices.GetRequiredService<IDataProtectionProvider>();
var distributedCache = app.ApplicationServices.GetRequiredService<IDistributedCache>();
var dataProtector = dataProtectionProvider.CreateProtector(
typeof(OpenIdConnectMiddleware).FullName,
typeof(string).FullName, schemeName,
"v1");
var dataFormat = new CachedPropertiesDataFormat(distributedCache, dataProtector);
///
/// Azure AD Configuration
///
var clientId = Configuration["AzureActiveDirectory:ClientId"];
var tenantId = Configuration["AzureActiveDirectory:TenantId"];
Redirect = Configuration["AzureActiveDirectory:TenantId"];
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = schemeName,
DisplayName = "Azure-AD",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = clientId,
Authority = $"https://login.microsoftonline.com/{tenantId}",
ResponseType = OpenIdConnectResponseType.IdToken,
StateDataFormat = dataFormat,
});
app.UseIdentity();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
This is the login.
[HttpGet]
public async Task<IActionResult> ExternalLogin(string provider, string returnUrl)
{
var context = this.HttpContext.Authentication;
List<AuthenticationDescription> schemes = context.GetAuthenticationSchemes().ToList();
returnUrl = Url.Action("ExternalLoginCallback", new { returnUrl = returnUrl });
// start challenge and roundtrip the return URL
var props = new AuthenticationProperties
{
RedirectUri = returnUrl,
Items = { { "scheme", provider } }
};
//await HttpContext.Authentication.ChallengeAsync(provider, props);
return new ChallengeResult(provider, props);
}
In my opinion ,we shouldn't directly pass the username/password directly from other Idp to azure AD for authentication as a security implementation .And even Azure AD supports the Resource Owner Password Credentials Grant ,it's only available in native client. I suggest you keep the normal way and don't mix them .