MVC application Authorization for ApiController doesn't work - asp.net-mvc

I'm building an MVC application that has standard Controllers and APIControllers in the same project. All functionality works fine, I can call Controllers or APIControllers no issues.
What I'm trying to achieve is to make sure that if you call /api/products from outside it shouldn't be accessible, only authorized users can call it.
I added and Authorize attribute to my api controller and I still can call it and get results from a client application like Postman. You can see my code below.
[Authorize] //System.Web.Http
public class ProductsController : ApiController
[HttpGet]
public IHttpActionResult Get()
Here is what I have in my Startup.Auth.cs file.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
If I commented out this code, I get a proper response from the APIController, but I also cannot login.
<Error>
<Message>Authorization has been denied for this request.</Message>
</Error>
Not sure what I'm missing. Per my understanding I don't need to create a custom Filter for my API controllers, I should be able to use a built in Authorization functionality. Let me know if you need more details or code samples.

I think that the auth for your MVC controllers is defined by Startup.Auth.cs while for the API controllers is at WebApiConfig.cs.
Make sure that on WebApiConfig.cs in the Register() method you have a call to config.SuppressDefaultHostAuthentication(); which prevents the configured authentication method on your MVC controllers to work on ApiController calls.
Also note that you must need, in certain cases, to add the [AllowAnonymous] attribute in some API calls like login, register, etc.

Interesting findings. I tried to call the API Controllers that are required authentication from within the application without authorization and I received a proper response, Not Authorized. I tried to call the API endpoint using Fiddler, the same result.
So for some reason, Postman still can get results without any errors. I created even a new API endpoint the same behavior. I'm going to refrain from using the Postman because even it's a great tool it creates a lot of confusion, and as a result, I wasted a few hours.
Hopefully, my example is going to save somebody else time.

Related

OWIN MVC5 with AAD RedirectUri not being redirected

Using OOTB MVC5 with AAD template without any modification.
What I want to do is add additional custom claims after AAD's authentication back to my application. The ConfigureAuth:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
RedirectUri = "https://localhost:44336/Account/Signin",
PostLogoutRedirectUri = postLogoutRedirectUri
});
}
In the Azure portal, the Reply URLs for the App is entered as above as well. The actual behavior of the app is that after the AAD login, the redirect just back to the app's root /, and does not hit the Signin action on the Account controller.
Any ideas why? Something I missed?
Do you also have a web.config or appsettings.json where the reply url/redirecturi is set? If so, ensure that it matches there as well.
Are you working only locally or is this a published app? Make sure that you have the published app URL included in the app registration and config as well. Also make sure that these are all https and not http (seems obvious but easy to miss).
Ensure that your homepage URL is listed correctly.
It's hard to diagnose this without seeing your Accounts Controller. Can you please provide this here?
Please check out this repository if you haven't already. It describes in detail how to accomplish what you are trying to achieve.
Also check out this Youtube tutorial.

GetExternalLoginInfo() returning null

I have registered my mvc app with https://apps.dev.microsoft.com/ and (when on localhost),after updating all NuGet packages managed to authenticate users (using a clientid and secret) using microsoft authentication - that works fine! lets forget the time wasted to discover that I had to use https://localhost:xxxx/signin-microsoft - thought I had to supply my callback method endpoint.
Now, I had to do the same thing however authenticating users with my app registered on Azure Active Directory in the section App Registrations.
Note: Users are not registered in azure but on a different domain however using microsoft authentication. I just changed the client id and secret to specify those generated on azure while registering the app. My Callback method is being accessed after signing in HOWEVER, the loginInfo object which I need to read the email of my user is NULL. I made sure i had the latest updates of packages, and i tried to search spending 3 days finding only applications which make use of tenants id, authority etc.
I just need to use individual accounts signing with microsoft authentication with an application registered on AAD (Azure active directory - registered apps section). I know it works because i've seen it working with php on other apps, but with microsoft code/libraries its not.
btw i tried adding scopes, calling synchronous to no good. I also tried inspecting the incoming data and its saying access denied and i'm pretty sure that client id and secret are ok. maybe the reply url is wrong? but any other callbacks i supply result in bad request and the callback method is at least being triggered. I also enabled "Sign Users In" and "Sign in and read users profile" permissions from azure as well. I'm running out of ideas. any help would be much appreciated. thanks
Code is the same code which is given to you when creating a new mvc web application using individual accounts i.e. In Startup.Auth.cs: i have this important part and other code
var myObj = new MicrosoftAccountAuthenticationOptions()
{
ClientId = "xxx",
ClientSecret = "xxx",
};
myObj.Scope.Add("openid");
myObj.Scope.Add("email");
// myObj.Scope.Add("User.Read");
app.UseMicrosoftAccountAuthentication(myObj);
In AccountController.cs i have this method which is initializing the request to microsoft
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
return new ChallengeResult(provider,
Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl
}));
}
and this (part of a) method which is handling the callback response:
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
var loginInfo = await
AuthenticationManager.GetExternalLoginInfoAsync();
//...
}
ok solved. the solution i provided earlier in my question works for an application registered on https://apps.dev.microsoft.com/ . when i registered my app on AzureAD it had to be done in a different way following this method: https://learn.microsoft.com/en-us/azure/active-directory/develop/guidedsetups/active-directory-aspnetwebapp

AuthenticationManager.GetExternalLoginInfoAsync() on google aspnet mvc5 returns null

I've developed an ASPNET MVC 5 application using default Visual Studio 2015 Template and Google authentication. All works fine in the development environment but on real the call after external authentication AuthenticationManager.GetExternalLoginInfoAsync() sometimes returns null.
Normally it returns null on the central hours of the day (from 08:00 to 20:00) but I haven't found a pattern because sometimes works at that time. I've watched the developer console, but there are not a lot of requests (22 in the last 12 hours) and all are successful.
I've tried some solutions from other StackOverflow threads but they didn't work. Also, I can try them only on the night because is a personal project and then the connection is successful and I can't reproduce the issue.
The code is standard:
On Startup
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
var google = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "xxxx",
ClientSecret = "xxxx",
Provider = new GoogleOAuth2AuthenticationProvider()
};
google.Scope.Add("email");
app.UseGoogleAuthentication(google);
}
On ExternalLoginCallback
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
Log.Debug("AuthenticationManager.GetExternalLoginInfoAsync()");
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
Log.Error("AuthenticationManager.GetExternalLoginInfoAsync(): null");
return RedirectToAction("Login");
}
...
More info
I've created new Google credentials with another user and when I change the clientId and clientSecret, it works again... I don't know even when...
Yet more info
The problem is not on credentials, I "only" need to restart ASP.NET application to solve the issue, perhaps this new clue helps somebody to help me.
Not replicated
I've post the answer and it isn't in that OWIN's GetExternalLoginInfoAsync Always Returns null post, I've mentioned there the thread where I found the solution: ASP.NET_SessionId + OWIN Cookies do not send to browser
Finally (I think that) I've found the solution after a week with no failed login. All thanks to this StackOverflow thread. My solution has been inserting the following line on AccountController.ExternalLogin action:
Session["Workaround"] = 0;
In the thread above (and links provided there) found a better explanation of the bug when mixing sessions and cookies of ASPNET MVC and OWIN component.
Full controller service code:
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// https://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser
Session["Workaround"] = 0;
// Request a redirect to the external login provider
return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
}
I faced the Similar Issue with Visual Studio 2017 and .net MVC 5.2.4, Updating Nuget Microsoft.Owin.Security.Google to latest version which currently is 4.0.1 worked for me
Nothing worked for me tried all above combination...
But when updated all references dlls with latest on NUGET
it worked like charm!!
After updating Microsoft.Owin.Security.Google version 4.1.1 (I'm using .NET framework 4.8) it works for me
I updated to version 4.0.1 of the Microsoft.Owin.Security.Google package and I was having the same problem. I was getting null and access_denied. Turns out the problem was due to a space that I had copied over in the Client Secret. It took me 2 days and lots of unnecessary code changes to figure this out. I noticed that when you click copy (not just copy) and paste from Google, there is a space at the end of the Client Secret.
I had the same trouble going from version 4.0.1 to 4.1.1 of the Microsoft.Owin.Security.* packages.
This article SameSite in code for your ASP.net applications seems to offer some clues as to how to make set SameSite to avoid this error, but due to time pressure, I ended up having to go back to 4.0.1 and will have to revisit later.

OWIN Authentication Login Page

I have an ASP.NET MVC5 application I am working on right now that I am integrating OWIN authentication into. I currently have my app configured in the startup class as so
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, // "ApplicationCookie",
LoginPath = new PathString("/Producer/LogIn/")
});
This is working great. But I have a few areas in my code where I need to know the login path. I am not looking to directly call a method to log out the user and redirect them, I just need the value of the PathString.
I could hard code it of course as its a value that wouldn't change often, (if ever) but I hate to do that and I want a solution that I can reuse in other projects cleanly. I would prefer to be able to access this value programmatically.
TIA.

Double authentication with Web app & Web API needed?

I'm stuck on how to solve following problem.
I'll start with describing what my app looks like in a general context.
[ ASP MVC (Angular App) ]
Uses Owin cookie
[ WEB API 2 ]
Uses Oauth Token Bearer
This scenario is happening:
User visits app and authenticates with a login form which lies in ASP MVC app and generates a cookie.
Now I've decided to use AngularJs to add a couple features which made me use $resources and Web API 2. However, those features are only available if user is authorized.
To the problem: Now I must use a token for each request to the Web Api 2 to access different methods within controllers. This means I must login the user again but this time through AngularJs. Using /token route.
How would I do this?
Should I take the cookie, check credentials in it and send it as a authentication request?
Can I do something within the form authentication, in the same method, in the Asp MVC app?
Please help me, this gave me a lot of overhead. Walking from a simple app to this in 30min. Can't even get my head around all stuff in the authentication.
Regards!
My WebAPI supports both token and cookie auth.
During startup I register the authentication like this:
private void ConfigureAuth(IAppBuilder app)
{
//Token
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
});
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
// this is to ensure that a 401 response is sent if the
// user is not authenticated, rather than redirecting to
// a logon page.
}
},
CookieDomain = ".example.com" //might not need to set this
});
}

Resources