I making a facebook login using the new setup in ASPNET mvc 5. When the user login I can see that there is a request for the default public profile, but all I'm getting back is the token, username and nothing else.
I thought that by default I would also get information like first and lastname. And if added to the scope also the email.
On the callback from facebook I'm having this code to extract the information:
var authManager = HttpContext.GetOwinContext().Authentication;
var loginInfo = await authManager.GetExternalLoginInfoAsync();
Thanks to the comment, I was able to find out that I needed to look into the identity of the callback to get the requested information
var loginIdentity = await authManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
This will provide everything beside the logintoken/provider.
I needed to look at the ExternalIdentity and not the ExternalLoginInfo. The post is updated with this info. Also important to add email to the scope
Related
I have this controller
[Route("Authentication")]
public class AuthenticationController : Controller
{
and this action
[HttpGet("SignOut")]
public async Task<IActionResult> SignOut([FromQuery] string sid)
{
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectDefaults.AuthenticationScheme);
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme);
return View();
This works as expected.
But when I configure a SignedOutCallbackPath for my OpenId authentication that has the same route, it doesn't work anymore. The constructor of my controller is not called, the action is not hit and the result in the browser is a blank page (code 200) with html/head/body, but all empty, that doesn't match any template or view.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.HttpOnly = true;
})
.AddOpenIdConnect(options =>
{
options.SignedOutCallbackPath = "/Authentication/SignOut";
Is the SignedOutCallbackPath not supposed to be a view of my own?
The callback paths in the OpenID Connect authentication scheme are internal paths that are used for the authentication flow of the OpenID Connect protocol. There are three of those:
CallbackPath – The path the authentication provider posts back when authenticating.
SignedOutCallbackPath – The path the authentication provider posts back after signing out.
RemoteSignOutPath – The path the authentication provider posts back after signing out remotely by a third-party application.
As you can see from my explanation, these are all URLs that the authentication provider uses: They are part of the authentication flow and not to be directly used by your users. You also don’t need to worry about handling those things. The OpenID Connect authentication handler will automatically respond to these requests when the authentication middleware runs.
This means that when you change a callback path to some path that is a route of one of your controller actions, then the authentication middleware will handle that request before your controller gets involved. This is by design, so that you do not need to worry about these routes as they are mostly internal.
You just have the option to change those paths if you cannot or do not want to use the default values.
Now, there are two possible things I can think of that you could have meant to change instead:
SignedOutRedirectUri: This is the URL the user gets redirected to after the sign-out process is completed. This basically allows you to send the user to some view with e.g. a message “you were successfully signed out”, to show that the sign-out is done.
You can also set this as part of the AuthenticationProperties that you can pass to the SignOutAsync.
CookieAuthenticationOptions.LogoutPath: This is the URL that is configured to the actual URL that users can go to to sign out of the application. This does not really have that much of an effect though.
Otherwise, it’s really up to you to send users to your /Authentication/SignOut URL. You can put a button into your layout that goes there for example, to offer users a sign out functionality at all times.
Your Action is expecting parameter which is not passed by your callback, the parameter is seemingly not used within the action either, so you can either omit the parameter or make it optional
[HttpGet("SignOut")]
public async Task<IActionResult> SignOut()
{
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectDefaults.AuthenticationScheme);
await ControllerContext.HttpContext.SignOutAsync(Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationDefaults.AuthenticationScheme);
return View();
}
For Azure AD I found this post logout redirection behavior:
It doens't work for root accounts of the tenant, that is my personal account, which created Azure
subscription.
But it works for new accounts I created inside of my
subscription.
I am trying to implement a way for users to change their email in AspNetCore so on the Account Management screen I have the change function that will call GenerateChangeEmailTokenAsync on the user manager, then sends the email with the link containing the Token and UserId.
My problem is how do I allow the link the change the email address to the new address since ChangeEmailAsync requires the new email address be entered.
What is the best practice way of implementing this functionality? I do not want to send over the new email address in the email link but also do not want to make them type the email again. Hopefully someone has done this, I cannot find it anywhere only, and it should be very simple.
I know it is late answering this, but I was looking for it myself before, and thought I leave the answer here for others.
The GenerateChangeEmailTokenAsync method takes the new email as part in the hash of the token.
Next you create a link that contains the token, the new email and the old email
var token = await _userManager.GenerateChangeEmailTokenAsync(user, model.NewEmail);
var resetLink = Url.Action("ChangeEmailToken", "account", new {token = token, oldEmail = user.Email, newEmail = model.newEmail }, protocol: HttpContext.Request.Scheme);
Next you send this link to the user in an email.
When clicked, the user hits the method named in the link (here "ChangeEmailToken" on AccountController:
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> ChangeEmailToken([FromQuery] string token, [FromQuery] string oldEmail, [FromQuery] string newEmail)
Next you need to verify the token, and -if succesful- update the email address.
var result = await _userManager.ChangeEmailAsync(user, newEmail, token);
The normal flow is to let the user update profile as usual.
If the user updated their email address then that new email address needs to be verified.
That is when you generate the token with GenerateChangeEmailTokenAsync.
You send that token as a link to the new email address.
When the user clicks the link in the new email it takes them back to you site which automatically confirms the token and verifies the new email address.
I've got an app that has been running using CredentialsAuthProvider() for a while.
Today, I'd like to add twitter and facebook as options for people to create an account. I've got the scaffolding in place.
//Register all Authentication methods you want to enable for this web app.
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(),
new TwitterAuthProvider(appSettings),
new FacebookAuthProvider(appSettings)
}));
//Provide service for new users to register so they can login with supplied credentials.
Plugins.Add(new CustomRegistrationFeature());
The endpoints /api/auth/twitter and /api/auth/facebook work, and I see that a user session is created by visiting /api/auth once the OAuth process is complete HOWEVER...
No user is created in my UserAuthRepository (OrmLiteAuthRepository). The User Id stored in the session is invalid. Any method decorated with the [Authenticate] attribute causes a 404 (User not found) error to be returned.
I would expect that a user is created with the First/Last/Email obtained from Facebook or Twitter. Am I misunderstanding something?
ServiceStack v4.0.38
A few things to check would be the oauth.CallbackUrl has been set correctly taking into account the multiple providers you have registered. Eg http://localhost/auth/{0}.
Also you should check if your IDbConnectionFactory used with your OrmLiteAuthRepository has also been registered with the IoC container. Eg
var dbFactory = new OrmLiteConnectionFactory(
"~/App_Data/db.sqlite".MapHostAbsolutePath(),
SqliteDialect.Provider);
container.Register<IDbConnectionFactory>(dbFactory);
var authRepo = new OrmLiteAuthRepository(dbFactory);
container.Register<IUserAuthRepository>(authRepo);
authRepo.InitSchema();
I wanted to update this thread to say that I've upgraded to ServiceStack 4.0.42 and this issue has resolved itself.
I have a ZF2 project and I'm using SocalNick/ScnSocialAuth (https://github.com/SocalNick/ScnSocialAuth) to enable social authentication.
The auth flow is ok. I've already got the user to be authenticated using his or her social login (for example, Facebook).
Now I need to know how can I get access to user's profile data from my Controllers an Views. I suspect that it must be using HybridAuth module (that is also loaded), but I couldn't find out how.
Can anyone help me?
The solutions proposed by Adam Lundrigan are OK. But there is another one that I would like to let registered here.
The following piece of code can be called from a controller's action:
$hybridAuth = $this->getServiceLocator()->get('HybridAuth');
// if the user is connected, authenticate will return an instance of the provider adapter
$adapter = $hybridAuth->authenticate('facebook');
if (!$adapter->isUserConnected()) {
// do something sensible for a logged out user...
return $this->redirect()->toRoute('some-logged-out-route');
}
$userProfile = $adapter->getUserProfile();
I would like to thank Nicholas Calugar for showing me this alternative solution.
Two of the possible approaches to get access to that data at the time the user signs up:
You could attach an event listener to the registerViaProvider event, which receives the HybridAuth adapter as a parameter. The simplest way to achieve this would be to attach it during your module's onBootstrap, something like this:
$sem = $e->getApplication()->getEventManager()->getSharedManager();
$sem->attach('ScnSocialAuth\Authentication\Adapter\HybridAuth', 'registerViaProvider', function($event) {
$provider = $event->getParam('provider');
// $provider is the HybridAuth provider the user authenticated
});
Then extract the user's profile data and store it somewhere.
Another possibility is: ScnSocialAuth's HybridAuth adapter (ScnSocialAuth\Authentication\Adapter\HybridAuth) implements a <service>ToLocalUser method for each supported service, so you could override ZfcUser's entity to add some extra fields then extend the HybridAuth adapter class to override each of those methods and populate your ZfcUser entity with the necessary profile data.
I am creating my own website and blog and I want for first time just me in database (my name and password) and maybe later some registration for others but first log in just for me and administration with authorization. I don´t want to use Membership from MS. I want try to create my own from start so I am looking for guide for beginners but I found big guides with roles, rights. I want just small example with check username, password in database with log on data.
Thanks for help
Libor
Even if you don't want to use the membership and role provider data store you can still utilize the authentication. Trust me, it's a lot easier than building your own. Here's how it works:
We'll say you already have your user storage setup for retrieving the username and their password. For the sake of simplicity I'm going to pretend you have a static class called DataLayer that contains your data retrieval methods for pulling info from the database (or whatever storage you use).
First you need a way to let the user log in. So set up a page with username and password fields. Then in the action method that the page posts to set up a quick if statement:
if (DataLayer.UserExists(userModel.Username))
{
User userFromDB = DataLayer.GetUser(userModel.Username);
if (userFromDB.Password == userModel.Password)
{
FormsAuthentication.SetAuthCookie(userFromDB.Username, checkBoxRememberMe.Checked);
//Use userFromDB as the username to authenticate because it will
//preserve capitalization of their username the way they entered it
//into the database; that way, if they registered as "Bob" but they
//type in "bob" in the login field, they will still be authenticated
//as "Bob" so their comments on your blogs will show their name
//the way they intended it to.
return "Successfully logged in!";
}
}
return "Invalid username or password.";
Now that they are authenticated you can just use Page.User.Identity.IsAuthenticated in your code to find out if they are logged in. LIke this:
if (User.Identity.IsAuthenticated)
{
DataLayer.PostBlogComment(User.Identity.Name, commentBody);
//Then in your controller that renders blog comments you would obviously
//have some logic to get the user from storage by the username, then pull
//their avatar and any other useful information to display along side the
//blog comment. This is just an example.
}
In addition, you can lock out entire action methods or even whole controllers to users that are authenticated through the forms authentication provider. All you have to do is add tags like these to your action methods/controllers:
[Authorize]
public ActionResult SomeActionMethod()
{
return View();
}
The [Authorize] attribute will prevent users that are not logged in from accessing that action method and it will redirect them to your login page. You can use this same attribute to filter out roles if you are using the built in roles provider.
[Authorize(Roles="Admin, SalesReps")]
public ActionResult SomeActionMethod()
{
return View();
}
These attributes can also be added above the controller class to apply it's logic to the entire controller.
EDIT: To log a user out all you need to do is call FormsAuthentication.SignOut();
Hey #Bibo, good for not choosing the Membership providers. I think a UserService or similar which provides methods for creating, authenticating users and some few more methods should be enough. As a suggestion, use password hashing and a password salt for the user´s password. Here is a good link to look at. Also have a look at this answer I gave some time ago.
Good luck!
EDIT: The rememberMe parameter should be named keepMeSignedIn instead.
This article on forms authentication gives you loads of info for creating your own simple security system, especially the bit about FormsAuthenticationTicket.
http://support.microsoft.com/kb/301240