I am using NancyFx to make a simple website were users can login and out using an ajax control.
I have read the documentation on Forms Authentication with Nancy and I think I have completed all the required steps.
Install the Nancy.Authentication.Forms package
Implement an IUserMapper
Implement routes to handle login and logout
Configure and enable Forms Authentication
I am having an issue where after calling login, requests do not have a current user set.
I can see a cookie set after the login is executed. However the user mapper is not getting called. I have tried requesting routes with and with out the this.RequiresAuthentication(); and still no user.
Here is my Bootstrapper Implementation for step 4
public class Bootstrapper : DefaultNancyBootstrapper
{
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
container.Register<ISessionFactory>((c, p) => SessionFactory.Factory);
}
protected override void ConfigureConventions(NancyConventions conventions)
{
base.ConfigureConventions(conventions);
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("assets", #"content/assets"));
conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("application", #"content/application"));
}
protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
{
base.ConfigureRequestContainer(container, context);
container.Register<IUserMapper, UserMapper>();
}
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
base.RequestStartup(container, pipelines, context);
var formsAuthConfiguration =
new FormsAuthenticationConfiguration()
{
RedirectUrl = "~/",
UserMapper = container.Resolve<IUserMapper>()
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
}
}
Here is my login & logout logic for step 3.
Post["/login"] = x =>
{
//Verify here, hardcode for testing
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginWithoutRedirect(user.Session);
return new { email = user.Email, authorized = true, status = "okay" };
};
Post["/logout"] = x =>
{
return this.Logout("~/");
};
My IUsermapper simply looks up a user from a database with the given id.
I can see it gets constructed when the RequestStartup resolves the IUserMapper but then there are never any calls to the get GetUserFromIdentifier function.
Any help would be great.
The GetUserFromIdentifier method is not being called because you are using the LoginWithoutRedirect extension. It is not the login that calls GetUserFromIdentifier but rather any subsequent redirect.
A more usual way of doing things would be:
string email = "test#example.com";
User user = ExecuteCommand(new FetchUser(email));
this.LoginAndRedirect(user.Session);
It is not expected that the login route would be accessed directly. Instead the user would normally request a protected resource, be authenticated and then redirected to the requested resource.
A couple of other points:
When I tried your code I got an error returning an anonymous type. Instead I needed to return the type as json, like this:
this.LoginWithoutRedirect(user.Session);
return Response.AsJson(new
{
email = user.Email,
authorized = true,
status = "okay"
});
This works fine, it logs in and returns your anonymous type as a json object and since there is no redirect then, as expected, it does not call GetUserFromIdentifier.
Finally, your /logout route should be protected using this.RequiresAuthentication(). It makes sense because only authenticated users need to logout. It will also protect you when GetUserFromIdentifier returns null - perhaps because a cached user has been timed out. RequiresAuthentication detects this null user and redirects back to Get["/login"].
Related
When the user submits his credentials to my api, I call an external api to authenticate the user. After that, a token gets generated on the external api and will be sent to me. For that I implemented the HandleAuthenticateAsync function from the AuthenticationHandler:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//before this: make call to external api to get the access token
var claims = new[] {
new Claim(ClaimTypes.Name, submittedToken),
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
I have implemented a custom AuthorizationHandler which I want to check for the access token that you got when you successfully authenticate. Note that the actual authentication and authorization is done by an external api which is a custom implementation. Here is the function:
public class IsAuthorizedRequirement : AuthorizationHandler<IsAuthorizedRequirement>, IAuthorizationRequirement
{
public AuthenticateHandlerHelperFunctions AuthenticateHandlerHelper;
public IsAuthorizedRequirement()
{
AuthenticateHandlerHelper = new AuthenticateHandlerHelperFunctions();
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IsAuthorizedRequirement requirement)
{
if(!context.User.HasClaim(c => c.Type == ClaimTypes.Name))
{
context.Fail();
return;
}
var token = context.User.FindFirst(c => c.Type == ClaimTypes.Name).Value;
if (!string.IsNullOrEmpty(token))
{
context.Fail();
return;
}
var checkedToken = await AuthenticateHandlerHelper.CheckAccessToken(token);
if (checkedToken == null)
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}
The CheckAccessToken function makes a simple HTTP Post Request to the external Api where I get back if the token is still valid or not. Is this a valid implementation especially when multiple users are using this? Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request? Currently I have no way to test this so I just wanted to know if I am on the right track for this. Thanks
Is this a valid implementation especially when multiple users are using this?
I strongly stand against this approach. Implementation like this mean you would call external API for validate and generate token(or cookie or any form of authenticated certificate) on external server for each and any of your request(which require authentication).
It's could be consider acceptable if we have some special cases on just some endpoints. But for the whole API/Web server. Please don't use this approach.
Especially the claims that I use: Are they created for each user or will the content inside ClaimsType.Name be overwritten each time a user makes a request?
They'll create for each request. As I can see in the code there are no part for generate cookie or some form of retaining user information for the client to attach next request afterward.
How to identify if authentication is not needed for current request?
We have a custom authentication handler, simplified here:
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
static readonly string[] DontCheckAuth = new string[] { "/servers", "/user/profile/", "/highscore/list", "/highscore/metadata/" };
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
/*
if url contains any of DontCheckAuth
then return AuthenticateResult.NoResult()
else
do custom auth
*/
}
}
So everything works OK, but HandleAuthenticateAsync is run on every request, even on URLs I have marked as [AllowAnonymous]. Currently I just check the URL and compare it to hard-coded list of URLs that does not require authentication.
How can I automate this process so I don't have to manually update the list of URLs (which I forget to do for new APIs)?
I created an ASP.Net Core 2.0 MVC using authentication providers as described here: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/
On localhost (i.e. when run via Visual Studio 2017) all works well. However, after deploying to Azure I found that the login providers stopped working (despite my setting up appropriate callback URIs; e.g. for Google I have https://localhost:44357/signin-google but also https://mysite.azurewebsites.net/signin-google, https://example.com/signin-google, and https://www.example.com/signin-google (as well as having setup the example.com domain and its www subdomain in Azure and configured SSL covering these domains). For Twitter I've changed the setup to the www subdomain only (as only 1 callback URL's allowed), and for LinkedIn I only have the domain and subdomain (i.e. I had to remove localhost; as LinkedIn only allows callback URI's under a single domain). I've also configured those keys/values which had been in my secrets.json under the Azure App Service's Application Settings.
Symptoms
On first login (aka registration), the user clicks the relevant provider's button after which new user entry appears in the AspNetUsers and AspNetUserLogins tables, and the user is directed to the page where they can associate their email. However, they're not logged in at that point; just registered. Subsequent attempts take them back to the email registration form; only clicking the Register button then returns an error message stating that the email's already registered (which is correct); but the user's still not signed in to the site.
I have the same issue with all providers; though after proving this focussed most of my ongoing on Google, just to limit the number of changing variables.
The only significant change I've made from the example was to refactor code in Startup.cs so that each provider's encapsulated in it's own method; so ConfigureServices contains:
ConfigureServicesAuthFacebook(services);
ConfigureServicesAuthGoogle(services);
ConfigureServicesAuthTwitter(services);
ConfigureServicesAuthMicrosoft(services);
ConfigureServicesAuthLinkedIn(services);
... and those methods look like this:
#region Authentication Providers
public void ConfigureServicesAuthFacebook(IServiceCollection services)
{
services.AddAuthentication().AddFacebook(x =>
{
x.AppId = Configuration["Authentication:Facebook:Id"];
x.AppSecret = Configuration["Authentication:Facebook:Secret"];
});
}
public void ConfigureServicesAuthGoogle(IServiceCollection services)
{
services.AddAuthentication().AddGoogle(x =>
{
x.ClientId = Configuration["Authentication:Google:Id"];
x.ClientSecret = Configuration["Authentication:Google:Secret"];
});
}
public void ConfigureServicesAuthTwitter(IServiceCollection services)
{
services.AddAuthentication().AddTwitter(x =>
{
x.ConsumerKey = Configuration["Authentication:Twitter:Id"];
x.ConsumerSecret = Configuration["Authentication:Twitter:Secret"];
});
}
public void ConfigureServicesAuthMicrosoft(IServiceCollection services)
{
services.AddAuthentication().AddMicrosoftAccount(x =>
{
x.ClientId = Configuration["Authentication:Microsoft:Id"];
x.ClientSecret = Configuration["Authentication:Microsoft:Secret"];
});
}
public void ConfigureServicesAuthLinkedIn(IServiceCollection services)
{
services.AddAuthentication().AddOAuth("LinkedIn", x =>
{
x.ClientId = Configuration["Authentication:LinkedIn:Id"];
x.ClientSecret = Configuration["Authentication:LinkedIn:Secret"];
x.CallbackPath = new PathString("/signin-linkedin");
x.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
x.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
x.UserInformationEndpoint = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)";
//x.Scope = { "r_basicprofile", "r_emailaddress" };
});
}
#endregion Authentication Providers
Question
How can I debug this issue given I cannot recreate the problem on localhost. Any hints on what the issue may be?
The way it works is your user first must assoicate their Google account with the user on your system. It sounds like this is working for you.
After that is done your code should preform some kind of ExternalLoginSignInAsync however this kind of depends on how you have your system set up.
Out of the box, where IsNotAllowed is true this means the email or phone number associated with the account which needs to be confirmed has not yet been confirmed. See ASN.NET Core 2.0 Facebook authentication ExternalLoginSignInAsync Fails (IsNotAllowed)
Take a look at the AccountController method ExternalLoginConfirmation and you'll see:
var user = new ApplicationUser(model.Email) { Email = model.Email };
Assuming you're happy for those signing up with existing logon providers, amend this to:
var user = new ApplicationUser(model.Email) { Email = model.Email, EmailConfirmed = true };
I am currently working on a vb.net MVC5 application and using WS-Federation to authenticate all the users from an ADSF 3.0 server. Everything is working fine; when the users try to access a secured controller marked with the AUTHORIZE attribute, the users are redirected to the STS login page, they login and they come back. I am able to read the CLAIMS provided by the ADFS server.
My problem is that i need to create a local entry in my database when a new authenticated user comes in after login to store additional informations. I do not want the user to register manually, if he is authenticated, it means i can trust this user.
The Startup.Auth.vb looks like this :
Partial Public Class Startup
Private Shared realm As String = ConfigurationManager.AppSettings("ida:Wtrealm")
Private Shared adfsMetadata As String = ConfigurationManager.AppSettings("ida:ADFSMetadata")
Public Sub ConfigureAuth(app As IAppBuilder)
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
.LoginPath = New PathString("/Home/Login")
})
app.UseWsFederationAuthentication(New WsFederationAuthenticationOptions() With {
.AuthenticationType = "ExternalCookie",
.Wtrealm = realm,
.MetadataAddress = adfsMetadata,
.Wreply = "https://myapp.com/Home/SSOLogin"
})
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType)
End Sub
End Class
In my Home controller, i have created the SSOLogin Action to be able to do what i need to do to test the presence of this user and create it if it does not exists
Public Function SSOLogin() As ActionResult
' Some code here to check if user exists or not and create it
End Function
I put a breakpoint in my action but it is never hit because the middleware handles and detect the postback before it hits the action method and does a Redirect 302 to the originally requested secured page.
The question
Are there any way to trap the callback in the WSFederation middleware or maybe add an event to the global.asax to do my user automatic creation only after authentication, not on all requests ?
Thanks for your time !
After the WsFederationAuthenticationHandler has validated the inbound token, you can register to receive a notification. WsFederationAuthenticationOptions.Notifications is where you can do that.
In startup.cs you want to set notification for: WsFederationAuthenticationNotifications.SecurityTokenValidated.
Patrice, here is some code that will give you an idea of how to hook the notification. this code is usually placed in startup.auth.cs.
var wsFederationOptions = new WsFederationAuthenticationOptions
{
Notifications = new WsFederationAuthenticationNotifications
{
SecurityTokenValidated = (notification) =>
{
var identity = notification.AuthenticationTicket.Identity;
var defaultName = identity.FindFirst ("<claim-that-identifies-user>");
// do work here
return Task.FromResult<object>(null);
}
},
MetadataAddress = wsFedMetadataAddress,
Wreply = host,
Wtrealm = clientId
};
app.UseWsFederationAuthentication(wsFederationOptions);
I'm writing some REST api for my cake 3.0 application, and I need to set $this->Auth->unauthorizedRedirect to false, as the manual says that this would prevent my application to redirect to login url for unauthorized requests.
http://api.cakephp.org/3.0/class-Cake.Auth.BasicAuthenticate.html
The problem is that I'm trying to set it in my Users controller, and it doesn't work:
class UsersController extends AppController {
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
$this->Auth->allow(['logout']);
// Change the authentication mode when using REST api
if(! $this->RequestHandler->accepts('html')) {
$this->Auth->unauthorizedRedirect = false;
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
}
}
}
This scripts works fine as detecting if a user is actually registered, but fails when I try to use wrong authentication data, showing the login form instead of throwing an error. What am I doing wrong?
Authentication and authorization are two different things
You are mixing up authentication and authorization, that's two different things. Logging in a user is authentication, testing whether a logged in user is allowed to access a specific action is authorization.
So the unauthorized redirect configuration applies to logged in users when accessing actions.
Handling unauthenticated requests
What you are looking for, ie throw an exception on unauthenticated requests, is done by the basic authentication adapter by default, so I assume that you actually aren't using this adapter!?
So if you are using a different adapter, this behavior is best implemented in either your controller where you are trying to identify the user
$user = $this->Auth->identify();
if (!$user) {
throw new ForbiddenException('Stop! Hammer time!');
} else {
$this->Auth->setUser($user);
}
or, in case you want the exception to be thrown for every controller, in a custom authentication adapters unauthorized() method, which is being invoked on unauthenticated requests before executing possible redirects. Quote from the docs:
Cookbook > Authentication > Handling Unauthenticated Requests
When an unauthenticated user tries to access a protected page first the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can handle sending response or redirection by returning a response object, to indicate no further action is necessary. Due to this, the order in which you specify the authentication provider in authenticate config matters.
If authenticator returns null, AuthComponent redirects user to login action. [...]
Here's a simple example that extends the form authentication handler:
src/Auth/MyCustomAuthenticate.php
namespace App\Auth;
use Cake\Auth\FormAuthenticate;
use Cake\Network\Exception\ForbiddenException;
use Cake\Network\Request;
use Cake\Network\Response;
class MyCustomAuthenticate extends FormAuthenticate
{
public function unauthenticated(Request $request, Response $response)
{
if(!$request->accepts('text/html')) {
throw new ForbiddenException('Ah ah ah! You didn\'t say the magic word!');
}
}
}
Controller
$this->loadComponent('Auth', [
'authenticate' => [
'MyCustom'
]
]);
See also
Cookbook > Authentication > Creating Custom Authentication Objects
Cookbook > Authentication > Using Custom Authentication Objects