Azure AD reply url failing on html handler - asp.net

Via ASP.NET I have created a startup file that will use Azure AD to log in a user
e.g.
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
{
ClientId = "42067b8d-b972-44e9-af86-ef60bc6d6fdb",
Authority = "https://login.windows.net/...com",
RedirectUri = "http://localhost:50560/content/story_html5.html",
PostLogoutRedirectUri = "http://localhost:50560/content/story_html5.html",
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.IdToken
});
}
And as you can see my RedirectUri in hitting a static file html file.
On my app registration in Azure portal my manifest for the replyUrls states
"replyUrls": [
"http://localhost:50560/content/story_html5.html"
],
So everything is working and connecting correctly.
(if I use a aspx for example the redirection would work)
However using the .html file I'm getting the error
HTTP Error 405.0 - Method Not Allowed
The page you are looking for cannot be displayed because an invalid
method (HTTP verb) is being used.
All I believe I need to do is add the html handler to Azure AD, does anyone know how to do this?
Thanks

This has nothing to do with Azure AD, but your configuration. Your end. Your Project. Your IIS config. Because sign-in response is a HTTP POST for security reasons. And static files handler in IIS does not accept anything beside GET for obvious reasons.
More information you will find here and there.
First, why would you want to redirect to a static page?! With the redirection after OIDC login, the IdP (Identity Provider, understand Azure AD in that case) sends valuable information which is needed by the OIDC middleware (understand the .UseOpenIdConnectAuthentication method) to be able to verify the token and initialize user session. By sending the sign-in response back to a static page you accomplish couple of things:
You cut out the OIDC middleware from the authentication - it is no longer able to process the response. Because it will not listen on static file requests. Static files are processed outside your OWIN authentication middleware.
Thus not able to verify authenticity of the user.
Thus not able to create secure cookie.
Thus not able to sign-in the user into your application.
Conclusion
Do not change the reply URL for your ASP.NET middleware, unless you explicitly and knowingly want to override the complete handling of sign-in responses.

Related

MSAL - Application can request permission even if it's not configured in 'API Permissions'

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.

Using Identity Server 3, ClaimsPrinciple null even after successful bearer token authentication

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

IdentityServer 3 refresh user with refresh token

We are trying to set up Identity Server 3 in the right way.
We got authentication working fine and we manage to retrieve the refresh token.
The client application is using Angular.
Now when the acces_token expires any calls to the rest api fails (we managed to get it to return 401) but we are wondering how to re-authenticate the user.
In our tests, any api call made from Javascript is failing (401) but as soon as the page is refreshed the whole mechanism is kicking in. We do see that we are redirected to the identity server but it does not show up the login page, we are sent back to the client application with new tokens apparently.
What I would like to do is to refresh the access token without having to force the user to refresh the page.
What I'm not sure though is whose responsibility is it? Is that the client application (website) or the angular application? In other word, should the application handle this transparently for Angular or should angular do something when it receives a 401, in which case, I'm not too sure how the information will flow back to the web app.
Any clue?
Additional Information: We are using OpenId Connect
I got it working!
As I said in the comments I used this article. The writer is referencing a very nice lib that I am using as well.
Facts:
Identity Server 3 is requesting the client secret upon access token refresh
One should not store the refresh_token or the client_secret on the javascript application as they are considered unsafe (see the article)
So I chose to send the refresh_token as en encrypted cookie sith this class (found of ST BTW, just can't find the link anymore, sorry...)
public static class StringEncryptor
{
public static string Encrypt(string plaintextValue)
{
var plaintextBytes = plaintextValue.Select(c => (byte) c).ToArray();
var encryptedBytes = MachineKey.Protect(plaintextBytes);
return Convert.ToBase64String(encryptedBytes);
}
public static string Decrypt(string encryptedValue)
{
try
{
var encryptedBytes = Convert.FromBase64String(encryptedValue);
var decryptedBytes = MachineKey.Unprotect(encryptedBytes);
return new string(decryptedBytes.Select(b => (char)b).ToArray());
}
catch
{
return null;
}
}
}
The javascript application is getting the value from the cookie. It then deletes the cookie to avoid that thing to be sent over and over again, it is pointless.
When the access_token becomes invalid, I send an http request to the application server with the encrypted refresh_token. That is an anonymous call.
The server contacts the identity server and gets a new access_token that is sent back to Javascript. The awesome library queued all other requests so when I'm back with my new token, I can tell it to continue with authService.loginConfirmed();.
The refresh is actually pretty easy as all you have to do is to use the TokenClient from IdentityServer3. Full method code:
[HttpPost]
[AllowAnonymous]
public async Task<JsonResult> RefreshToken(string refreshToken)
{
var tokenClient = new TokenClient(IdentityServerConstants.IdentityServerUrl + "/connect/token", "my-application-id", "my-application-secret");
var response = await tokenClient.RequestRefreshTokenAsync(StringEncryptor.Decrypt(refreshToken));
return Json(new {response.AccessToken});
}
Comments are welcome, this is probably the best way to do that.
For future reference - using refresh tokens in an angular (or other JS) application is not the correct way as a refresh token is too sensitive to store in the browser. You should use silent renew based on the identityserver cookie to get a new access token. Also see the oidc-client-js javascript library, as this can manage silent renew for you.

Issue with https on production environment

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

ASP.NET mvc 3 Anonymous Authorization not working

I have a strange issue that of course only occurs on our production box. It works on our test server and on my box.
I have an ASP.NET MVC 3 controller that is serving exposing a RESTful API. I have enabled anonymous users to call these service with the code shown below. Calling these methods via GET works just fine (using WebRequest). However, when trying to POST data (using HttpClient) it fails with a 401 error.
This web service is hosted within another IIS site which uses Windows Auth. But I configured this directory to allow Anonymous and disabled windows auth. It lives in /Areas/Services under the main site.
I have configured IIS to allow Anonymous authentication and even enabled it in the web.config. However, when I try to POST data to this controller, I get back "401 - Unauthorized: Access is denied due to invalid credentials". I don't want any credentials! Again, GET on this same controller works fine anonymously.
This seems to be a configuration issue (since it works in QA) but I do not know any other things to configure. I have been configuring IIS websites for anonymous/windows/forms auth for 10 years but have never run into anything like this before.
Here is the code that allows MVC 3 to serve these methods up to anyone:
[AuthorizeAnonymous]
public class LtWebsiteController : Controller
{
...
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class AuthorizeAnonymousAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!(filterContext.Controller is LtWebsiteController))
base.OnAuthorization(filterContext);
}
}
This is driving me nuts! Please help.
You are likely missing HTTP headers for NTLM authentication. I would configure HttpClient to send the right credentials as part of the request.
HttpClientHandler handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
HttpClient client = new HttpClient(handler);
It's confusing since you are enabling anonymous authentication. But, with Windows Authentication the request needs to have proper headers. A 401 tells me the server flat out rejects the HTTP request.
http://www.asp.net/web-api/overview/security/integrated-windows-authentication

Resources