I have a standard VS2013 MVC5 project with a Web Api 2 in it. The way the standard project is designed, the [Authorize] attributes simply return a 401 status code if the request is not authenticated, while a totally separate module sniffs for any 401 codes, halts them, and instead sends a 302 redirect to the login page specified in the Startup.Auth.cs file. That's ok for Mvc controllers, but really stinks for Web Api controllers because for example browsers will automatically redirect ajax requests to the login url, so you ultimately end up with a 200OK status even though the response text is just the html of the login page.
That makes it hard to write good javascript that can distinguish between a case where you just need to tell the user to log back in versus other kinds of errors. Ideally we should be able to tell based on the status code, but javascript never ever sees the 401 status. What is the best way to handle this?
My first thought was to write an authorization attribute but use status code 403 instead of 401:
public class ApiAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
base.OnAuthorization(actionContext);
}
else
{
actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Not signed in.");
}
}
}
Of course, specifications explicitly state that 403 is incorrect:
Authorization will not help and the request SHOULD NOT be repeated
My other thought is that maybe I should disable asp.net's 401 redirect module altogether and handle redirects in custom authorization attributes, because even for Mvc views it is lousy because it doesn't allow you to redirect to different login pages depending on where in the site the user is trying to visit.
Are there other, better approaches to handling this?
Here's what I was able to find with a bit more research. The 401 is intercepted by the OWIN middleware. But, OWIN does support branching configurations using the Map method. So in the Startup.cs file I have this:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map(new PathString("/api"), site => ConfigureAuth2(site));
ConfigureAuth(app);
}
}
where ConfigureAuth is the default configuration method that comes in the Startup.Auth.cs file, while ConfigureAuth2 is a duplicate of that method but with the LoginPath option left unspecified in the UseCookieAuthentication method, which looks like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
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))
}
});
According to the documentation, when the LoginPath is unspecified, 401 responses won't be intercepted for this branch.
So with this approach I'm branching all requests into two different configurations--all /api requests get configured not to redirect on 401 statuses, while everything else gets configured to redirect to the login page.
This SO question talked a bit about branching the configuration.
I'm still not sure if this is the best approach though.
Related
I'm using Microsoft's Azure API Management, specifically the delegation feature, to handle login requests so my website can handle authentication for users visiting my developer portal on APIM.
I'm simply trying to handle a "SignOut" request whereby Azure API Management redirects to my Delegation page as follows:
case "SignOut":
//this doesn't actually log the user out; suggestions?
return new RedirectToPageResult("Identity/Account/Logout");
The logout page only has a POST action:
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return Page();
}
}
I would prefer to leave this page alone and simply 'use it' for my logout action from the delegated page handling events from APIM.
Any idea how to handle the logout using a redirect or other method? I know I'm missing something simple here...
If I understand you correctly, I don't think you can do that without additional client logic. HTTP redirect that server returns is 302 response with Location header with a new URL. The way browsers react to that is by issuing GET request on provided URL. Where as your signout logic required POST request. The only way to reuse your current POST endpoint for signout would be to return 200 response with client-side script that would invoke POST on required endpoint.
Personally I would rather prefer to do logout right there inside your delegation endpoint.
I am implementing Google login for my .net core site.
In this code
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
I need a signInManager which is (by the code example) this:
private SignInManager<AppUser> signInManager;
I inject it via the constructor, and then I get this error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.SignInManager1[AppUser]' while attempting to activate 'AccountController'.
Googling learnt that I should include this
services.AddIdentity<AppUser, IdentityRole>()
.AddDefaultTokenProviders();`
But that gives me this error:
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.IUserStore1[AppUser]' while attempting to activate 'Microsoft.AspNetCore.Identity.AspNetUserManager1[AppUser]'.
And at that moment, I get the advice to add this:
.AddEntityFrameworkStores<ApplicationDbContext>()
But then I'm lost, because why does the SignInManager need a IUserStore, and should I add a
UserStore and a DBContext and an EntityFramework store, when I will not be using that (for my Google login)?
So the question is: can I also do my Google login without the Entityframework store?
If all you want to do is sign-in with Google, there's no need for SignInManager, UserManager or ASP.NET Core Identity itself. To achieve this, we first need to configure the Authentication services. Here's the relevant code for this, which I'll explain after:
Startup.cs
services
.AddAuthentication(o =>
{
o.DefaultScheme = "Application";
o.DefaultSignInScheme = "External";
})
.AddCookie("Application")
.AddCookie("External")
.AddGoogle(o =>
{
o.ClientId = ...;
o.ClientSecret = ...;
});
The call to AddAuthentication configures a DefaultScheme, which ends up being used as both the Application scheme and the Challenge scheme. The Application scheme is used when attempting to authenticate the user (are they signed in?). The Challenge scheme is used when a user is not signed in but the application wants to provide the option to do so. I'll discuss the DefaultSignInScheme later.
The two calls to AddCookie add cookie-based authentication schemes for both Application (our Application scheme) and External (our SignIn scheme). AddCookie can also take a second argument, that allows for configuration of e.g. the corresponding cookie's lifetime, etc.
With this in place, the challenge process will redirect the user over to /Account/Login (by default - this can be configured via the cookie authentication options too). Here's a controller implementation that handles the challenge process (again, I'll explain after):
AccountController.cs
public class AccountController : Controller
{
public IActionResult Login(string returnUrl)
{
return new ChallengeResult(
GoogleDefaults.AuthenticationScheme,
new AuthenticationProperties
{
RedirectUri = Url.Action(nameof(LoginCallback), new { returnUrl })
});
}
public async Task<IActionResult> LoginCallback(string returnUrl)
{
var authenticateResult = await HttpContext.AuthenticateAsync("External");
if (!authenticateResult.Succeeded)
return BadRequest(); // TODO: Handle this better.
var claimsIdentity = new ClaimsIdentity("Application");
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier));
claimsIdentity.AddClaim(authenticateResult.Principal.FindFirst(ClaimTypes.Email));
await HttpContext.SignInAsync(
"Application",
new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(returnUrl);
}
}
Let's break this down into the two actions:
Login
In order to arrive at the Login action, the user will have been challenged. This occurs when the user is not signed in using the Application scheme but is attempting to access a page protected by the Authorize attribute (or similar). Per your requirement, if the user is not signed in, we want to sign them in using Google. In order to achieve that, we issue a new challenge, this time for the Google scheme. We do so using a ChallengeResult that is configured with the Google scheme and a RedirectUrl, which is used for returning to our own application code once the Google sign-in process completes. As the code shows, we return to:
LoginCallback
This is where the DefaultSignInScheme from our call to AddAuthentication becomes relevant. As part of the Google sign-in process completion, the DefaultSignInScheme is used for setting a cookie that contains a ClaimsPrincipal representing the user as returned from Google (this is all handled behind the scenes). The first line of code in LoginCallback grabs hold of this ClaimsPrincipal instance, which is wrapped up inside an AuthenticateResult that is first checked for success. If everything has been successful so far, we end up creating a new ClaimsPrincipal that contains whatever claims we need (taken from Google in this case) and then signing-in that ClaimsPrincipal using the Application scheme. Lastly, we redirect to the page that caused our first challenge.
In response to a couple of follow-up comments/questions in the comments below:
Can I conclude that the SignInManager and UserManager are only used when using authentication with a database?
In some ways, yes, I think that's fair. Although it is possible to implement an in-memory store, it doesn't really make much sense with no persistence. However, the real reason not to use these classes in your situation is simply because you do not need a local user account for representing a user. That goes hand-in-hand with persistence, but it's worth making the distinction.
And what very different code from what I read in the book (which I used for setting up my Google login) and all the other answers I've read.
The documentation and the books cover the most common use-case, whereby you do want to store local users that can be linked to external accounts such as Google, etc. If you look at the SignInManager source, you'll see that it's really just sitting on top of the kind of code I've shown above (e.g. here and here). Other code can be found in the Default UI (e.g. here) and in AddIdentity.
I assume the LoginCallback gets called by Google. Does the HttpContext.AuthenticateAsync know how to check the data Google sends me? And as it's name is so generic, it looks like it knows how to do that for all external providers?
The call to AuthenticateAsync here doesn't know anything about Google - the Google-specific handling is configured by the call to AddGoogle off of AddAuthentication in ConfigureServices. After redirecting to Google for sign-in, we actually come back to /signin-google in our application. Again, this is handled thanks to the call to AddGoogle, but that code is really just issuing a cookie in the External scheme that stores the claims that came back from Google and then redirecting to our LoginCallback endpoint that we configured. If you add a call to AddFacebook, a /sigin-facebook endpoint will be configured to do something similar. The call to AuthenticateAsync is really just rehydrating a ClaimsPrincipal from the cookie that was created by e.g. the /signin-google endpoint, in order to retrieve the claims.
It's also worth noting that the Google/Facebook sign-in process is based on the OAuth 2 protocol, so it's kind of generic in itself. If you were to need support for more than just Google, you would just issue the challenge against the required scheme rather than hardcoding it to Google as I've done in the example. It's also possible to add additional properties to the challenge in order to be able to determine which provider was used when your LoginCallback endpoint is reached.
I've created a GitHub repository that contains a complete example that I built in order to write this answer here.
I have two applications
Client application build on ASP.NET MVC
Authentication server build on Web API + OWIN
Have planned authentication as follow
For user login client app will make a request to authication server with logged in credential.
Authication server will generate a token and will send back to client application.
Client application will store that token in local storage.
for each subsequent request client app will attached token kept in local storage in request header.
NOW, ON SERVER SIDE OF CLEINT APP I NEED TO VALIDATE THAT TOKEN COMES WITH EACH REQUEST IS NOT TEMPERED.
Please suggest me how to validate token in each request as i don't know the key the OWIN has used to generate the token.
Is is right to write code to validate token on client app or it should be on authication server.
I am planning to shift all user management code like register user, change password to authentication server so than we can re-use it for different client app- is it right design practice?
So far i have wrote below code to just to create a POC.
=========================OWIN configuration========
[assembly: OwinStartup(typeof(WebApi.App_Start.Startup))]
namespace WebApi.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = false,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new
OAuthBearerAuthenticationOptions());
}
}
}
==============================oAuth Provided========================
public class SimpleAuthorizationServerProvider: OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Please help,
Thanks,
#Paul
Please suggest me how to validate token in each request as i don't
know the key the OWIN has used to generate the token.
Your current setup, were you have added the app.UseOAuthBearerAuthentication() to the owin pipeline, will authenticate the user from the bearer token which is passed on each request for you.
The current user can then be found via HttpContext.Current.User.
Use the Authorize attribute to then decide which users are authorized on certain endpoints.
Here's an example where users with the role "user" are allowed to access
[Authorize(Roles="user")]
public class ValuesController : ApiController
{
}
Is is right to write code to validate token on client app or it should
be on authication server.
NO, you don't validate the token in client, if your user credentials are wrong you wont get a token at all. That's all you need to know.
And also, why should you want to validate the token in the client?
I am planning to shift all user management code like register user,
change password to authentication server so than we can re-use it for
different client app- is it right design practice?
Reusing a token provider is common. Why invent the wheel for every application? Build one great, or use a third party, and reuse it across your applications.
Use JSON Web Tokens (JWT) and claims identities, not random tokens that require keeping track of the issued tokens.
A JWT is like a passport issued by a trusted authority. The passport is signed/stamped, and you can verify that it was issued by this trusted authority and that it has not been tampered with. That means, the integrity of the access-right claim present in the token can be verified without keeping state anywhere. The only communication that needs to happen between the trusting application and the authority is an initial (secure) download of the authority's public key (used for signing the tokens).
It's also advisable that you use a standard claims schema, like OpenID Connect ( http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims )
A good book on the topic, which helped me a lot getting an understanding of all these concepts, can be found here: A Guide to Claims-Based Identity and Access Control.
One way to verify a token has not been tampered is to sign it using an asymmetric key pair, Identity Server uses this approach as seen here.
In your case if you are rolling your own authentication you will need to implement this yourself, and check on every request probably in a custom middleware that the token is valid.
If you create, sendback, save in localStorage and every thing about JWT Token as correct, you have to know that many ways are in .Net that you can to controlling per request.
Server side controlling:
If you are using Web API Core, in core you can create Middleware that runs as pipline in run time, and you can give context and check token that requested, for more infomation check: This.
If you use of Asp.net MVC, you can use ActionFilter in MVC(Asp.Net-Core have more advance ActionFilter too), that each requests goes through on and you can check every thisng abount request, for more information check: This.
ClientSide Conftolling:
After that you give Token after log in from server side, you have to save data in localstorage that your browser check per request that data, they advantage are the Expireation and every like this issue in token save in localstorage and you and browser can use of this for more information check: This.
GoodLuck.
I am trying to get a ASP.NET Core 2.1 web app running with Azure AD B2C.
I have this running after much reseach (and more trial and error) as the instructions found in the docs are a little off.
The sign-in/sign-up process works using the default scaffolding during the project setup wizard, plus the new Microsoft.AspNetCore.Authentication.AzureADB2C.UI Nuget, which resulted in a simplified, yet "back boxed" experience during startup.
The problem currently is that I am unable to make this work with a custom Reply URL that is different from "signin-oidc". I have read that "signin-oidc" is baked into the provider somehow, and is hence the default.
I have a OnboardingController with a Start action defined where I want the user to land after signing up, so I have done the following:
A) I tested that the Url localhost:12345/Onboarding/Start works. The
page is displayed correctly.
B) In appsettings.json I change AzureAdB2C's "CallbackPath": "/signin-oidc" to "CallbackPath": "/Onboarding/Start"
C) I go to the tenant and change the application's Reply URL to localhost:12345/Onboarding/Start. IMPORTANT side note: Unlike in the ADB2C samples and guides, you MUST append the Reply URL with either signon-oidc or a custom request path! Localhost:12345 is NOT ENOUGH!
I can confirm the authentication worked:
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: AzureADB2CCookie signed in.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 56.201ms
Then, when I manually navigate to /Onboarding/Start in the browser, I get
Error from RemoteAuthentication: Correlation failed.
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:12345/Onboarding/start
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Warning: .AspNetCore.Correlation. state property not found.
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler:Information: Error from RemoteAuthentication: Correlation failed..
1.
But I am never redirected to the intendend /Start page. instead I land back on the root homepage, just like what happens when I used 'signin-oidc'. Why is that and how do I make it stop going there?
2. How can I have the Reply URL be different depending on whether you sign IN or UP? I can't really use a sign-up policy for one and a separate sign-in policy for the other, because the reply URL is identical.
I am new to .Net Core and I am at a loss as to how to even debug this. Signing in with B2C seems to be an obscure process. Any insight would be greatly appreciated.
EDIT:
This is the custom account controller, which is not built into the Nuget package for AzureADB2C.
While the Nuget package provides an internal AccountController, it does not allow you to set a custom Reply URL. Yet using my own account controller does not work for me. I am also not getting the "Correlation failed" error either, instead I get no error at all.
[Route( "[controller]/[action]" )]
public class AccountController : Controller
{
private readonly AzureADB2COptions azureAdB2COptions;
private const string PolicyAuthenticationProperty = "Policy";
private string scheme = AzureADB2CDefaults.AuthenticationScheme;
public AccountController( IOptions<AzureADB2COptions> b2cOptions )
{
azureAdB2COptions = b2cOptions.Value;
}
[HttpGet]
[Route( "/[controller]/SignIn" )]
public IActionResult SignIn()
{
var callbackUrl = Url.Action( nameof( OnboardingController.Start ), "Onboarding", values: null, protocol: Request.Scheme );
var properties = new AuthenticationProperties { RedirectUri = callbackUrl };
properties.Items[PolicyAuthenticationProperty] = azureAdB2COptions.SignUpSignInPolicyId;
return this.Challenge( properties, scheme );
}
The startup.cs code for B2C is unchanged from what the default .NETCore 2.1 template generated for me:
services.AddAuthentication( AzureADB2CDefaults.AuthenticationScheme )
.AddAzureADB2C( options =>
{
Configuration.Bind( "AzureAdB2C", options );
} );
An authentication request that is passed from your web application to Azure AD B2C can contain two redirect URLs:
One (often known as the reply URL) that is passed in the "redirect_uri" parameter, which must be registered with Azure AD B2C, to which all authentication responses are returned from Azure AD B2C to your web application. The default for this is /signin-oidc.
Another (often known as the return URL) that is round-tripped in the "state" parameter, which doesn't have to be registered with Azure AD B2C, to which the end user is returned after your web application has handled the authentication response. An example of this is /Onboarding/Start.
Your web application can set the return URL as follows:
public class AccountController : Controller
{
public IActionResult SignUp()
{
return this.Challenge(
new AuthenticationProperties()
{
RedirectUri = Url.Action("Start", "Onboarding", values: null, protocol: Request.Scheme)
},
AzureADB2CDefaults.AuthenticationScheme);
}
}
The ChallengeResult object creates an authentication challenge for the Azure AD B2C authentication middleware that is added by the AzureADB2CAuthenticationBuilderExtensions.AddAzureADB2C method.
The first argument to the ChallengeResult constructor invokes the OpenID Connect authentication handler that is registered by the Azure AD B2C authentication middleware.
The second argument to this constructor sets the return URL to which the end user will be returned after the Azure AD B2C authentication middleware has handled the authentication response.
It is my understanding that you will need to enable the the Application Claim -> newUser.
This flag is only set to true when a user initially signs up. Once they are redirected back to your website, you will need to read the claim and redirect to your onboarding/start or other controller if they are an existing user.
You can change the redirect_uri to any action in your website, but the problem is that the uri is only used as an endpoint. The action at the uri is never executed. So I think it will be very hard, if not impossible, to change the routing at the redirect_uri.
But I found another solution to make sure your Onboarding Registration is executed, using the Authorize attribute with a policy like: [Authorize(Policy="HasUserId")]
Take a look at https://stackoverflow.com/a/57672145
But I am never redirected to the intendend /Start page. instead I land back on the root homepage, just like what happens when I used
'signin-oidc'. Why is that and how do I make it stop going there?
How can I have the Reply URL be different depending on whether you sign IN or UP? I can't really use a sign-up policy for one and a
separate sign-in policy for the other, because the reply URL is
identical.
I was facing very similar issue to yours. You can have a look at this answer
which says
"The CallbackPath is the path where server will redirect during authentication. It's automatically handled by the OIDC middleware itself, that means we can't control the logic by creating a new controller/action and set CallbackPath to it . Below is the general process :
During authentication, the whole process is controlled by OpenID Connect middleware , after user validate credential in Azure's login page ,Azure Ad will redirect user back to your application's redirect url which is set in OIDC's configuration , so that you can get the authorization code(if using code flow) and complete the authentication process . After authentication , user will then be redirected to the redirect URL ."
Here you can find an example how I managed to handle redirects to different routes after login via AzureAd.
I have a OWIN self-hosted application which has a front-end where users can sign up. Nancyfx does the logic for routing and Models, and in the documentation I saw that Nancyfx comes with 3 types of authentication.
Forms (Nancy.Authentication.Forms)
Basic (Nancy.Authentication.Basic)
Stateless (Nancy.Authentication.Stateless)
I've settled on the Stateless Authentication, and following this example I tried to set up a basic form of authentication.
I wanted to expand further on this, using JWT to have some basic info handy and as a form of basic authentication (e.g. client has token so he's verified.), but this is where I run into a few problems.
The way I authenticate Home -> login -> redirect upon success causes my Response.Header.Authorization to be cleared, not allowing me to catch the token in my custom Bootstrapper.
Code:
protected override void RequestStartup(TinyIoCContainer requestContainer, IPipelines pipelines, NancyContext context)
{
AllowAccessToConsumingSite(pipelines);
StatelessAuthentication.Enable(pipelines, requestContainer.Resolve<IStatelessAuthConfigurationFactory>().Config());
}
//Returns ClaimsPrincipal or Null;
public StatelessAuthenticationConfiguration Config()
{
if(_stat == null)
{
_stat = new StatelessAuthenticationConfiguration(VerifyToken);
}
return _stat;
}
Since my authorization header disappears every request, I would need to persist the JWT. I figure it's possible using OWIN environment or Nancy context, but would this be advisable + what would the effect be for a multi-user environment regarding security.
OWIN has it's own authentication Manager that I could use, I've experimented with it, but it tends to provide a cookie upon successful sign in, which it doesn't seem to revoke on Logout. I just ran into a few issues overall with it, so I settled on NancyFx authentication. (not really a problem as a more general remark I suppose)
Thanks in advance for any help!
Regarding (1), if you roll your own redirection after a successful login, consider setting the Authorization header during the redirect, e.g.
return Response.AsRedirect("/").WithHeader("Authorization", token);
It's actually the responsibility of the client to hold the valid JWT token after authentication. Returning it as a cookie (and deleting it upon logout) could make things easier in terms of client-side implementation and avoid the token persistence issue.
Regarding (2), not really, it's not necessary. JWT tokens are self-contained, and that's why they're useful in stateless auth scenarios.