I have promoted a test .NET Web Api to an Azure application service and included an app registration under Azure Active Directory. I then went to do some testing locally and noticed that Azure wanted to use the reply URL in the app registration after login. The reply URL in the app registration is the URL for the application service. My local instance will be something like https://localhost:44377/. How are you supposed to test changes locally after doing an initial deploy to Azure? All I can think to do is create another app registration for testing, use my local host reply URL, then update my web.config to point to that development app registration. Then prior to publishing again, update the web.config back to the other app registration.
Below is the code I used for authentication which was based on the standard template from a simple MVC project. The values app registration are being used for the redirect URL but maybe I am supposed to override those values below while testing?
public class AccountController : Controller
{
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
If you want to test locally, just add localhost as a reply URL and ensure that the web.config also lists localhost.
Please refer to this repository if you have not done so already: https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect
You can have multiple reply urls by specifying which you want to use in the authentication request. You do that when configuring your authentication in Startup.cs. You need to add a RedirectUri to your OpenIdConnectAuthenticationOptions.Notifications.RedirectToIdentityProvider
var openIdOptions = new OpenIdConnectAuthenticationOptions
{
//...
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.RedirectUri = "<current reply uri>";
return Task.FromResult(0);
}
}
// ...
};
That reply uri can be pulled from your web.config or generated dynamically using context.Request.
If you want to use a different AD App after going to production, you can have two apps and put the client id and secret in the web.config.
Related
I am new to Blazor applications. My scenario is, I have to get back the Windows logged user name from the hosted Blazor WASM application on IIS.
Below are the steps I followed.
Created a ASP.Net Web API Core project and decorated it with [ApiController]. In the launchsettings.json file changed the following attributes.
"windowsAuthentication": true,
"anonymousAuthentication": false,
Enabled Windows and disabled Anonymous authentication in the IIS where my Web API is hosted.
In the Startup.cs file of the WebAPI configured the below
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In the controller method, added a property
private readonly IHttpContextAccessor _httpContextAccessor;
and initialized it in the constructor as below.
public UsersController(IUsersRepository usersRepository,
IWebHostEnvironment webHostEnvironment, IHttpContextAccessor httpContextAccessor)
{ _usersRepository = usersRepository;
_webHostEnvironment = webHostEnvironment;
_httpContextAccessor = httpContextAccessor;
}
Finally in the API method
[HttpGet("getusername")]
public string GetUserByName()
{
var principal=_httpContextAccessor.HttpContext.User;
return principal.Identity.Name;
}
When I browse the above Web API from IIS and call the above API method, I get the proper user name with domain/username.
Everything looks good so far, the actual issue is with the Blazor Webassembly project which invokes the above Web API method. When the Blazor WASM is hosted on the IIS( the same server as the above Web API) and calls the API method to get the user name I always get a null value. I made similar changes as Web API to launchsettings.json file and in IIS Authentication( Enabled Windows and disabled Anonymous authentication) in Blazor WASM.
I am invoking the API as below
public async Task<string> GetUserName()
{
return await JsonSerializer.DeserializeAsync<string>
(await _httpClient.GetStreamAsync($"api/users/getusername"), new
JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}
Since this is going to be a intranet site, we did not want to do any authentication by having the user to login through a Login page. The site address is set to auto login in the Chrome browser settings.
Could you guide me in the right direction, not sure what I am missing.
I have created a small .NET Core 3.1 console application using the MSAL library which requests scope api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2
Custom API exposes two scopes
api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope1
api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2
I have also configured another application named test-app in Azure Active Directory which represents my console application.
I have configured only one API permission (api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope1) for this application. My understanding is with this configuration in place client app will only be able to request for scope test1 and it won't allow test-app to request for scope2
Below is screenshot
This is my code:
//<PackageReference Include="Microsoft.Identity.Client" Version="4.13.0" />
namespace console_client
{
class Program
{
static void Main(string[] args)
{
#region Azure AD parameters
var clientId = "dddeefa5-d95c-4931-a53d-2382deee27c3";
var tenant = "-- MY TENANT ID--";
var instance = "https://login.microsoftonline.com/";
#endregion
var client = PublicClientApplicationBuilder
.Create(clientId)
.WithDefaultRedirectUri()
.WithAuthority($"{instance}{tenant}")
.Build();
List<string> scopes = new List<string>();
try
{
// I was under impression that this call will throw as exception as
// this app is requesting 'testscope2' which is not included in API Permissions
// while configuring test-app in Azure Active Directory (dddeefa5-d95c-4931-a53d-2382deee27c3 )
// But I was able to retrieve token back with testscope2 in it.
scopes.Add("api://55a047a1-a0d1-4b6b-9896-751a848e1e06/testscope2");
var authenticationResult = client.AcquireTokenInteractive(scopes).ExecuteAsync().Result;
Console.WriteLine($"Interactive Access token is : {authenticationResult.AccessToken}");
}
catch (Exception ex)
{
System.Console.WriteLine($"******* {ex.Message}");
}
}
}
}
Question
Am I missing anything? Why am I getting the access token back even if the app doesn't have permission configured?
Thanks
TL;DR it is a feature.
With the v2 endpoint / MSAL, you can request for scopes that are not defined in your app manifest.
The ones in your app registration are the static permissions required by your application.
But your application can also request dynamic permissions at login time.
The user/admin would still need to consent to that of course, an app won't get a permission without consent.
Your app seems to be a single-tenant app so this doesn't really make a difference for you.
It is mainly for multi-tenant SaaS applications that can require the minimum needed permissions in the app registration/manifest, and then request more permissions for opt-in features as they are needed.
By the way, if you want to use the permissions defined in your app registration, you can request a special scope: api://55a047a1-a0d1-4b6b-9896-751a848e1e06/.default (your app ID URI or client id + "/.default").
This will make AAD look at your app registration to decide which permissions to check consent for.
I am working on an ASP.NET MVC application which uses ADFS authentication and have the following error in our log files in production and I'm trying to figure out what the cause of this issue is, as I believe it is preventing some users from accessing our application.
The error is as follows:
System.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '08/13/2018 12:59:35'
Current time: '08/13/2018 13:15:34'.
While I can't be sure, since I don't have timestamps of when the error happened, I believe it is causing the classic ASP.NET Server Error in '/' Application and it's the only error I'm seeing in our logs that would correlate with that page appearing.
As I'm searching Stack Overflow, I see references to JWT authorization which is not what our application is using. Or at least we aren't using anything that explicitly uses JWT for authentication, it may be what's happening under the hood. I also see some posts which state that if the authentication server and application server times are not in sync this error can occur; I am working with my IT team to verify these server's clocks are in sync and will update accordingly.
Our application uses a singular MVC route to serve our Angular application, and only enforces authentication on that landing page; our API controllers do not have specific authorization requirements on them (I know, bad security practice, that's a whole other conversation I'm trying to have with my team's architect).
While I wait for the information on the clocks, are there any other possible options I can investigate?
OWIN Startup code
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
// Workaround for this bug: http://katanaproject.codeplex.com/workitem/197
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieSecure = CookieSecureOption.Always,
CookieName = "Adfs Cookie Name",
});
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = WebConfigurationManager.AppSettings["WSFederation:MetadataAddress"],
Wtrealm = WebConfigurationManager.AppSettings["WSFederation:Realm"],
SignOutWreply = WebConfigurationManager.AppSettings["WSFederation:Realm"],
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = ctx =>
{
if (IsAjaxRequest(ctx.Request))
{
ctx.HandleResponse();
}
return Task.FromResult(0);
}
}
});
return app;
}
private static bool IsAjaxRequest(IOwinRequest request)
{
var query = request.Query;
if (query != null && query["X-Requested-With"] == "XMLHttpRequest")
{
return true;
}
var headers = request.Headers;
if (headers != null && headers["X-Requested-With"] == "XMLHttpRequest")
{
return true;
}
return false;
}
}
Finally figured out what the issue was! But first, some background. The web app I'm building integrates with a WPF application via a browser control in the application. The browser control is a tab that is not selected on the initial load of the application, but the does at least make a request and get redirected to ADFS for authentication. However, the browser wasn't completing the redirect from ADFS to my app until after the browser tab is activated.
Why does all of this matter? Well the ADFS token was configured with a 1hr lifetime. So what happened was users would open the WPF app, and automatically authenticate with ADFS and get a token generated. However, if they didn't activate the tab within that 1hr lifetime the token would expire before the redirect completed. I think this could also happen if I opened a tab in a browser, signed in to ADFS, and then immediately moved to a different tab before my app would have time to be served. Ultimately, it's a weird edge case for my application, but the root of the problem was a token getting issued but not validated by my app server until after it had already expired.
I have a test console app which I'm pointing at a local instance of Identity Server 3 to request an access token. The following code does this and returns my token fine (passing a single scope "scope.test.client").
static TokenResponse GetClientToken(string clientId, string clientSecret, string[] scopes)
{
var uri = new Uri(string.Concat(ID_BASE_URI, ID_URL_TOKEN));
var client = new TokenClient(
uri.AbsoluteUri,
clientId,
clientSecret);
return client.RequestClientCredentialsAsync(string.Join(" ", scopes)).Result;
I then use this token to call an API also running locally. This takes the TokenResponse obtained above and passed it to this method:
static void CallApi(string url, TokenResponse response)
{
try
{
using (var client = new HttpClient())
{
client.SetBearerToken(response.AccessToken);
Console.WriteLine(client.GetStringAsync(url).Result);
}
}
catch (Exception x)
{
Console.WriteLine(string.Format("Exception: {0}", x.Message));
}
}
The API (an ASP.NET WebApi project) uses an Owin Startup class to enforce bearer token authentication for all requests:
appBuilder.Map(baseApiUrl, inner =>
{
inner.UseWebApi(GlobalConfiguration.Configuration);
// Enforce bearer token authentication for all API requests
inner.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://identityserver/core",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "scope.test.client" }
});
});
It also ensures all API requests are handled by a custom authorize attribute:
GlobalConfiguration.Configuration.Filters.Add(new DefaultApiAuthorizeAttribute());
Debugging this API, the first line in my overridden OnAuthorize method (in DefaultApiAuthorizeAttribute) is this:
var caller = actionContext.RequestContext.Principal as System.Security.Claims.ClaimsPrincipal;
If I break on this line I can see that actionContext.RequestContext.Principal is always null. However, I can see that ((System.Web.Http.Owin.OwinHttpRequestContext)actionContext.RequestContext).Request.Headers contains an Authorization header with the bearer token passed from my console app.
So it would seem that the API project is not authenticating the bearer token. Certainly the Identity Server logs suggest it isn't being hit at all after issuing the initial access token. So I'd appreciate your expert advice about why this might not be happening, or at least some pointers about where to look.
I suspect it might have something to do with SSL. Both sites are hosted locally under self-signed SSL certs, although Identity Server is configured to not require SSL and uses the idsrv3test.pfx development certificate for signing. I do have another test MVC web app which delegates authentication to the same IS3 instance which works fine locally, so I believe my IS3 instance is configured correctly.
You need to call UseIdentityServerBearerTokenAuthentication before you call UseWebApi. When you set up an OWIN Middleware Pipeline, the order is important.
In your case, Web API will be handling your requests before they get sent onto Identity Server (if they get sent on at all).
I imagine a range of possible issues could have the impact I described, but in my case I was able to find the cause by adding a diagnostics log to my consuming API. This led me to discover that the problem was an assembly conflict. The Owin middleware was looking for a Newtonsoft.JSON assembly with version 8.0.0.0 but my consuming API (actually running on top of a CMS intance) was using 7.0.0.0.
For anyone else who wants to find the answer fast, rather than spend hours tweaking configurations, here's the documentation that describes how to add this logging: https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html
I have tried many options and this is my last resort to see if any of the community members have any ideas.
I have .NET MVC 5 application in which I use a Filter to force HTTPS on each unsecured request.
Here is the scenario:
Access my application at say, http://portal.mywebsite.com
It is redirected to third party (auth0) SSO provider for authentication. If the user is not already authenticated, he is redirected to the SSO login page.
The user enters valid credentials, authenticated.
The above scenario works perfectly. But the issue is If I access the same application with https say https://portal.mywebsite.com, it fails. To be precise, it fails to retrieve a ExternalIdentity (ExternalCookie) on the server.
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
if (externalIdentity == null)
{
throw(new Exception("Could not get the external identity. Please check your Auth0 configuration settings and ensure that " +
"you configured UseCookieAuthentication and UseExternalSignInCookie in the OWIN Startup class. " +
"Also make sure you are not calling setting the callbackOnLocationHash option on the JavaScript login widget."));
}
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, CreateIdentity(externalIdentity));
return RedirectToLocal(returnUrl);
}
Also, accessing the application with https on my test environment works and not the production environment.
All my web applications are hosted as Azure WebRoles.
I tried Fiddler to watch the requests between working and non-working to see if I can find any useful information in identifying the issue but no success.
Any thoughts or ideas that I could try to help me narrow down the cause?
Thanks in advance!
There is a bug in Microsoft's Owin implementation for System.Web. The temporary fix is addressed here at github.com/KentorIT/owin-cookie-saver
Someone had the same issue .AspNetApplicationCookie and ASP.NET_SessionId not created