Redirect after confirmation in ASP.NET WEP API controller - asp.net-mvc

I have web api account controller where I confirm an user email
[System.Web.Http.AllowAnonymous]
[System.Web.Http.HttpGet]
[System.Web.Http.Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IHttpActionResult> ConfirmEmail(string userId = "", string code = "")
{
if (string.IsNullOrWhiteSpace(userId) || string.IsNullOrWhiteSpace(code))
{
ModelState.AddModelError("", "User Id and Code are required");
return BadRequest(ModelState);
}
IdentityResult result = await this.AppUserManager.ConfirmEmailAsync(userId, code);
if (result.Succeeded)
{
return Ok();
}
else
{
return GetErrorResult(result);
}
}
}
This method is called when user clicks the confirmation link from email. After that I want to redirect it to "ConfirmidSuccessfully" page
In MVC we could do it like:
return View("ConfirmidSuccessfully");
There are other ways to redirect like:
var response = Request.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri("/ConfirmidSuccessfully");
return response;
Actually there are 2 questions:
Is it good to redirect from web api method according to WEB API, or there's better approach
What is the most appropriate way to do it

It is not a good practice to redirect to a view or a web page when you are using REST hence ASP.Net Web API.
Just return a successful status code to the client. Let the client do the redirection itself.
For example, if you are using an AngularJS App to connect with your Web API then after a call to email confirmation finished with success, redirect to the web page/view by using the web page URL you store in the client side.
[EDIT]
Based on your comment
I use angularjs, but the call to email confirmation comes from user's email, not from client.
Then you must generate the confirmation email on server side by making the host's url to be your Angular JS app host. e.g. myangularjsapp.com/emilconfirmation/token. Send an email with this URL to your user.
With URL like that the user is redirect from his email to your AngularJS App. When he hit the app you initialize a call to the ASP.Net Web API by retrieving the token from your AngularJS App url.

Since you are returning IHttpActionResult, you can return redirect in the action and this is the preferable way:
return this.Redirect("/path/to/redirect");

Related

Passing a parameter to Identity Server 4 Login Page

I have a client application that is using IDSVR4 for authentication. I need to store the user's username(in a different process from the login through IDSVR) on the client in a session or whatevr other client-side data storage mech so the user doesn't have to enter the details everytime he logs in from that specific browser.
How can i pass the username from the client to identity server?
You can add the username to the request parameters. If you're using asp.net in the client, you may use the notification event RedirectToIdentityProvider then add your username to the ProtocolMessage. Something like this:
RedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Parameters['username'] = "John";
return Task.CompletedTask;
}
Any value you add to the parameters will be accessible in IdentityServer through the IIdentityServerInteractionService method GetAuthorizationContextAsync
Like this, in your IdentityServer controller:
public async Task<IActionResult> Login(string returnUrl){
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var username = context.Parameters['username'];
...
}
You can pass custom parameter to the authorize endpoint , for code sample you can refer to my reply here .
If you are not using the OpenID Connect OWIN Middleware , you can directly put the custom parameter into the authorize request :
http://localhost:xxxx/account/login?returnUrl=/connect/authorize/callback?client_id=mvc2&redirect_uri=http%3A%2F%2Flocalhost%3A49459%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20api1%20offline_access&response_mode=form_post&nonce=xxxx&state=xxxx
On identity server side you can parse returnUrl and easily get the parameter .

Login and logout via SignalR

I have this SignalR / MVC web application I'm building.
I currently have login and logout functionnality using ajax calls.
I would like to have those transit via SignalR instead. Is that possible? I'm having a hard time understanding how the auth cookie is going to be handled.
Currently my controller action login does this
[HttpPost]
public ActionResult Login()
{
var nickname = Request.Params["nickname"];
var password = Request.Params["password"];
// [Some database stuff...]
FormsAuthentication.SetAuthCookie(agencyMember.Id.ToString(), true);
}
So to give this a try, I've setup my logout function in my hub:
public class Proto1 : Hub
{
[Authorize(RequireOutgoing = false)]
public string Logout() {
FormsAuthentication.SignOut();
return "ok";
}
This does'nt work, when I debug, I get a "Response is not available in this context." exception and the logout does not go through.
Am I going wrong about this?
Thanks!
What's your motivation for wanting to do login and logout with SignalR?
SignalR Hub method invocations do not necessarily correspond to distinct HTTP requests (they can be triggered by a WebSocket message for example). This means that it's not always possible to modify response headers or set cookies when you are inside of a Hub method.
I would suggest continuing to login and logout using Ajax calls.

owin external login not redirecting to client application

I have a dedicated web api identity server used by potentially multiple client applications. Now I am trying to get the flow for Facebook authentication work. The process is very similar to the one found in microsoft examples. Here's an image of the flow:
Basically my problem is that once last call is finished successfully, browser is not redirected to client application (localhost:9053).
ExternalLogin in API:
[OverrideAuthentication]
[HostAuthentication(DefaultAuthenticationTypes.ExternalCookie)]
[AllowAnonymous]
[Route("api/externalLogin", Name = "externallogin")]
public async Task<IHttpActionResult> GetExternalLogin(string provider)
{
if (User == null || !User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider, this);
}
ExternalLoginData externalLogin = FromIdentity(provider, User.Identity as ClaimsIdentity);
if (externalLogin == null)
{
return InternalServerError();
}
if (externalLogin.LoginProvider != provider)
{
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
return new ChallengeResult(provider, this);
}
IdentityUser user = await UserManager.FindByIdAsync(externalLogin.UserName);
Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
ClaimsIdentity oAuthIdentity = await _userManager.CreateIdentityAsync(user, OAuthDefaults.AuthenticationType);
ClaimsIdentity cookieIdentity = await _userManager.CreateIdentityAsync(user, CookieAuthenticationDefaults.AuthenticationType);
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", user.UserName }
};
var properties = new AuthenticationProperties(data);
Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
return Ok();
}
Given a user who is signed into their facebook account and have already accepted permission for this application, it performs the tasks in the image, which ends up coming all the way to the bottom of this method and returning Ok() (200). After this step, I expected owin facebook middleware to intercept and redirect the user to the client user localhost:9053 but that is not happening and it just hangs there.
Any ideas anyone?
When you register your application with Facebook, they ask you to provide a Site URL. (Go to https://developers.facebook.com/, then look under: Settings > Website > Site URL)
The Site URL is also used by Facebook as a server whitelist. In other words, Facebook will only respond to a request with a redirect if the redirect_uri matches the Site URL exactly. The reason they do this is to protect you against an attack called an Open Redirect.
Make both servers share the same hostname by proxying one of them.

How to obtain email address from Facebook using ASP.MVC and DotNetOpenAuth

I'm trying to set up authentication in my app. I have the posting to the external website working (facebook and google)
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Get), ValidateInput(false)]
public ActionResult OpenIdLogOn(string returnUrl)
{
var redirectUrl = "~/Companies/LogIn";
var provider = Request.Form["provider"];
if (Common.IsNull(provider))
return View();
OpenAuth.RequestAuthentication(provider, redirectUrl);
return View();
}
This works by figuring out what button was clicked (Login with facebook / Google) Then requests authentication. When the authentication has finished and I get a post back, I try to retrieve the email address.
public ActionResult LogIn()
{
var redirectUrl = "~/Companies/LogIn/OpenIdLogOn";
var response = Response;
var userEmail = Request.Params[16];
if (!String.IsNullOrEmpty(ReturnUrl))
{
Response.Redirect("~/Bloggers");
}
ViewBag.OAuthProviders = OpenAuth.AuthenticationClients.GetAll().ToList();
return View();
}
The following will work if I log in with google -
var userEmail = Request.Params[16];
but facebook doesn't send the email back. Well I cant see it in the request. There must be a better way of doing this than using Request.Params[16];
also. Help would be much appreciated.
Without being able to see what OpenAuth is, it's pretty hard to say what the new code should look like. But suffice it to say that you should absolutely never scrape the email address directly from the request. All security offered by OAuth and OpenID are worthless when you do that. You need to get the email address from the security library you're using.
For Google, that means getting it from the FetchResponse or ClaimsResponse extensions of the response.
For Facebook, that means using the access token returned from the login to fetch the email address from Facebook and verify the audience field to make sure the access token isn't being redirected to your site as part of a user spoofing attack.

DotNetOpenAuth Authentication against Google Apps OpenID using MVC

I'm trying to approximate Single Sign on. Currently, the most workable solution involves the user imputing details on my site before being sent off to goggle Apps to authenicate.
I'm using dotnetopenauth to send and recieve the requests
This means that it takes no account of google apps login cookies should they be present.
There are several questions covering this already. But none seem to have an answer to this.
This is the current code (note that I'm constraining the possible domains that can be used to just google apps for my domain):
public ActionResult Authenticate(string returnUrl)
{
string input=Request.Form["openid_identifier"]+"#example.com";
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
var response = openid.GetResponse();
if (response == null)
{
// Stage 2: user submitting Identifier
Identifier id;
if (Identifier.TryParse(input, out id))
{
try
{
return openid.CreateRequest(input).RedirectingResponse.AsActionResult();
}
catch (ProtocolException ex)
{
ViewData["Message"] = ex.Message;
return View("Login");
}
}
else
{
ViewData["Message"] = "Invalid identifier";
return View("Login");
}
}
else
{
// Stage 3: OpenID Provider sending assertion response
switch (response.Status)
{
case AuthenticationStatus.Authenticated:
FormsAuthentication.SetAuthCookie(response.ClaimedIdentifier, false);
var results = userstable.Select(response.FriendlyIdentifierForDisplay);
if (results.Count() > 0)
{
Session["FriendlyIdentifier"] = results.ElementAt(0).UserFName;
return RedirectToAction("Index", "Home", results.ElementAt(0).UserID);
}
else
{
UsersDataModel user = new UsersDataModel();
user.OpenID = response.ClaimedIdentifier.ToString();
user.UserID = Utils.HashToBase64(response.FriendlyIdentifierForDisplay);
user.Type = "Empolyee";
userstable.Insert(user);
//return RedirectToAction("Register");
}
if (!string.IsNullOrEmpty(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
case AuthenticationStatus.Canceled:
ViewData["Message"] = "Canceled at provider";
return View("Login");
case AuthenticationStatus.Failed:
ViewData["Message"] = response.Exception.Message;
return View("Login");
}
}
return new EmptyResult();
}
The only difference between this code and the normal pattern are the following lines:
openid.DiscoveryServices.Clear();
openid.DiscoveryServices.Insert(0, GoogleAppsDiscovery); // it should be first if we don't clear the other discovery services
These lines correctly setup OpenId to process the response from google apps.
As I said, this works only if the user inputs his/her email address first. i can't find a way of automatically redirecting to the Google Apps Login
In normal gmail, if I pass the user off to: https://www.google.com/accounts/o8/id instead of processing the input, google will have the user input login details on their site rather than on mine. It will skip that if there are cookies and just pass the user right back to my site with no fuss.
I would like to have the same behavior for my site for the google apps logins.
Additionally, it would be nice if I could constrain the logins to my google apps domain.
There are a couple of URLs floating around:
https://www.google.com/accounts/o8/site-xrds?hd=example.com
or
https://www.google.com/a/example.com/o8/id
But even with those lines of code I pointed out earlier, dontnetopenauth refuses to see an openid endpoint at either address.
Even the sample in dotnetaopenauth for webforms still requires user input.
Any help would be most appreciated.
You can't have no user input and any Google Apps Domain work for the user, because the user may be logged into 15 Google Apps for Domains accounts, and then no one would know which account the user means to log in with.
But since you say you'd like them to only be able to log in with your own domain, that's easy. Skip asking the user, and just pretend "example.com" was the user input and pass that into DotNetOpenAuth. It doesn't have to be an email address. Only the domain name is used anyway.
That said, that doesn't guarantee no one from other Providers or domains can log into your site. To do that, you need to filter on the IAuthenticationResponse that comes back and make sure its Provider property matches the one value you intend to allow. Otherwise, "unsolicited assertions" can still come in.

Resources