I need to limit access for my MVC site and accept only requests that come from another site.
Inside a main site i have a link that will re-direct in child site users that are already authenticated.
So i would like to process this request and authorize users if they clicked in this link.
I'm thinking to share a token in both applications, but when i re-direct user i can't attach this token to the request (true ?) but only in query string.
My query string have alredy the userId and two integer values.
Here is my code from Main site
public void Redirect(HttpResponse response)
{
var userId = HttpUtility.UrlEncode(_encryptor.Encrypt(_userId.ToString(CultureInfo.InvariantCulture)));
var year = HttpUtility.UrlEncode(_encryptor.Encrypt(_compilationYear.ToString(CultureInfo.InvariantCulture)));
var month = HttpUtility.UrlEncode(_encryptor.Encrypt(_compilationMonth.ToString(CultureInfo.InvariantCulture)));
const string baseUrl = "http://localhost:63415/Home/Index"; //external site
response.Redirect(String.Format("{0}?userId={1}&year={2}&month={3}", baseUrl, userId, year, month));
}
and in child site side
public HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
//if request come from the main site
var encUserId = Request.QueryString["userId"];
var encYear = Request.QueryString["year"];
var encMonth = Request.QueryString["month"];
//else show unauthorized page error
}
}
What about set cookie with token ?
I can change code but not in aspx where i have
<foo:MyButton Alt="redirect"
Enabled="true" Height="22" Id="btn"
OnClickJs="Foo()" runat="server"
/>
Now i'm thinking to perform post action via jQuery inside Foo() method... but i have the error
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Sharing a token could be a good solution in case that this token is dynamic and not static (new token for each redirect) because otherwise once somebody gets this token he will be able to share it and access yous site directly.
About sharing token there are 2 ways that I am aware of:
Sharing the same storage: your main application stores a token and a user identity somewhere in database and your secondary MVC application can retrieve this token from DB and invalidate it.
Implementing a common library that will be used for encryption\decryption of user data: Once user clicks on a link in your main site his credentials are encrypted into a token, this token is posted to your MVC site which is going to use the same library to decrypt the token and extract user credentials from there. If you are going to use this approach you will need to use some strong encryption algorithm (probably symmetric one that uses the same key for encryption\decryption because you probably don't want to deal with key distribution and using a public key to encrypt a message is not a solution in your case). You also have to insure that algorithm that you are using doesn't generate the same token for the same user every time.
when i re-direct user i can't attach this token to the request (true ?)
If you are using a redirect from server you are right, but you could take a different approach (Assuming that your main site is MVC):
When user clicks on a link (your link should have a "_blank" target so it could be opened in a new browser window in your main site you go to an action in your main site that generates a token and passes it as a model to it's view:
public class RedirectModel
{
public string Token { get; set; }
public string Url { get; set; }
}
public ActionResult Redirect()
{
var model = new RedirectModel{
Token = GenerateToken(),
Url = //url of your secondary site login action
}
return View(model);
}
View has a form that is going to be submitted on document load:
#model RedirectModel
<html>
<body onload="document.redirect.submit()">
<form method="POST" action="#Model.Url" novalidate autocomplete="off" name="redirect">
<input type="hidden" name="token" value="#Model.Token">
</form>
</body>
</html>
Of course the login action of your secondary MVC site has to decrypt a token and extract user credentials from it.
Related
I am using Identity Server 4 for authentication for both a website and WPF application. On the website, I want users to have the ability to check a Remember Me box when signing in, but I don't want that for the WPF application. I have the logic to disable that checkbox on the front end, but am having trouble in my controller. I have this function
[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
LoginViewModel _vm;
_vm = await BuildLoginViewModelAsync(returnUrl);
//if(Client_id == "wpf") <- this is what I need help with
//{
// _vm.AllowRememberMe = false;
//}
return View(_vm);
}
This controller contains
private readonly IIdentityServerInteractionService mInteraction;
private readonly IClientStore mClientStore;
private readonly IAuthenticationSchemeProvider mSchemeProvider;
private readonly IEventService mEvents;
Any help would be appreciated
You can get the client id from the AuthorizationRequest returned from the IIdentityServerInteractionService as follows using your code snipet:
var context = await mInteraction.GetAuthorizationContextAsync(returnUrl);
_vm.AllowedRememberMe = context.ClientId != "wpf";
However, you would be better off placing this logic in your BuildLoginViewModelAsync method where the view model is constructed rather than setting the property after construction.
I don't believe the client_id is directly available from any IS4 constructs in the Login method. However, depending on your OIDC flow, it's likely that your client_id was passed as part of the "returnUrl" parameter. Look at your return URL and see if it's there.
For example, I have a spa website connecting to IS4 that shows a returnURL of:
https://localhost:8080/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3DspaClient%26redirect_uri%3Dhttps...(long url continues)
You can see that it contains the "client_id" parameter with a value of "spaClient". Simply parse the returnUrl using your code of choice (e.g. RegEx) and extract the client_id from there. I don't have any WPF experience, so it may behave differently and not pass this parameter.
Once a WebAPI access token is generated, how does WebAPI validate that token for the next request? I wonder if I can use an [Authorize] attribute, it must compare the token sent by the client with the token at the server side, if stored somewhere. Does it just check if token is present and not its value?
Bearer token
First of all, your Identity Provider or Token Provider which issues the authorization tokens needs to have the same machine key settings as the Web Api application for encryption/decryption:
<machineKey
decryptionKey="B7EFF1C5839A624ED0268917EDE82F408D2ECBFAC817"
validation="SHA1"
validationKey="C2B8DF31AB9624D8066DFDA1A479542825F3B48865C4E47AF6A026F22D853DEC2B3248DF268599BF89EF78B9E86CA05AC73577E0D5A14C45E0267588850B"
/> </system.web>
Because under the hood Bearertoken uses MachineKey encryption.
In other words if you dont have the same settings, your web api won't be able to decrypt the token (validate it).
This is done automatically by:
Microsoft.Owin.Security.OAuth.dll
using middleware.
You can use the Authorize Attribute on your web api controllers/actions if you want simple authorization with Usernames or roles like this:
[Authorize(Roles="Administrators,Managers",Users ="Mike,Laura")]
If you want custom authorization, then you have to implement a custom authorization attribute which will handle the custom authorization in your web api. If the user is not allowed to pass you will return a 401 UnAuthorized Response:
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate","Bearer location='http://localhost:8323/account/login'");
For example:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class CustomAuthorizeAttribute : System.Web.Http.Filters.AuthorizationFilterAttribute
{
public RulesExampleEnum[] Rules { get; set; }
public string Id { get; set; }
.....
// Summary:
// Calls when a process requests authorization.
//
// Parameters:
// actionContext:
// The action context, which encapsulates information for using System.Web.Http.Filters.AuthorizationFilterAttribute.
public virtual void OnAuthorization(HttpActionContext actionContext);
public virtual Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken);
and register it in your webApiConfig.cs
config.Filters.Add(new CustomAuthorizeAttribute());
and apply it on Web Api controller or action:
[CustomAuthorize(Id = "AnyId", Rules = new RulesExampleEnum[] { RulesExampleEnum.Rule1, RulesExampleEnum.Rule3 })]
public IEnumerable<object> Get()
{...
Once access token is generated, client must include the access token inside Header for each request.
Client may set the access token inside Authorization HTTP Header.
On the server side, you should create class to handle the Authorization, which is a derived class from System.Web.Http.AuthorizeAttribute, something like below :
public class AuthorizationHandlerAttribute : AuthorizeAttribute
{
string AccessTokenFromRequest = "";
if (actionContext.Request.Headers.Authorization != null)
{
// get the access token
AccessTokenFromRequest = actionContext.Request.Headers.Authorization.Parameter;
}
string AccessTokenStored = "";
// write some code to get stored access token, probably from database
// then assign the value to a variable for later use
// compare access token
if (AccessTokenFromRequest != AccessTokenStored)
{
// if the token is not valid then return 401 Http Stasus
// or simply call base method
base.HandleUnauthorizedRequest(actionContext);
}
}
Then you use the newly created class and attach it on controller or action you wished to protect from unauthorized access.
public class UsersController : ApiController
{
[AuthorizationHandler]
public User Get(int id)
{
// only request with valid access token will reach this
}
}
The secret key is transmitted in the header of the request from the client to the server, and the contents are validated at the server at each request where the [Authorize] attribute is used.
You can use a tool like Fiddler from Telerik (free) to see the data being transported, but not the content (since its encrypted). Inspecting raw web traffic is invaluable when working with MVC / WebAPI, so I highly recommend it. Here's a link to Fiddler, although other similar tools exist as well.
http://www.telerik.com/fiddler
To answer the second part of your question, the server absolutely checks the contents of the secret key before allowing the request to proceed as authorized.
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.
I am building ASP.NET MVC 4 application. I use Simple Membership provider to manage authentication and authorization within the system. What are the ways of changing the password in this approach. I found a ChangePassword method which takes three parameters, including original password, to operate.
Is there any other way to override/change the password for the user without actually knowing original password?
ChangePassword is used when a user wants to change their password - and the current password is their evidence to allow this to happen (think Change Password Screen).
I think the most direct way to do this is to call WebSecurity.GeneratePasswordResetToken() and pass the result into WebSecurity.ResetPassword, along with the new password.
var token = WebSecurity.GeneratePasswordResetToken("UserName");
var result = WebSecurity.ResetPassword(token, "NewPassword");
There is a detailed article on how to implement password reset/change with SimpleMembership in MVC 4 here. It also includes source code you can download.
This examples uses email to send a URL to the user to click on for password reset. This is more secure than just having the user enter the old password and new password directly on the website because it is another verification of the user. This alleviates the scenario where someone gets a hold of the user password and locks them out by changing the password. This also allows the user to reset the password in the case where they have forgotten the password.
The code to send the email with the link would look something like this.
[AllowAnonymous]
[HttpPost]
public ActionResult ResetPassword(ResetPasswordModel model)
{
string emailAddress = WebSecurity.GetEmail(model.UserName);
if (!string.IsNullOrEmpty(emailAddress))
{
string confirmationToken =
WebSecurity.GeneratePasswordResetToken(model.UserName);
dynamic email = new Email("ChngPasswordEmail");
email.To = emailAddress;
email.UserName = model.UserName;
email.ConfirmationToken = confirmationToken;
email.Send();
return RedirectToAction("ResetPwStepTwo");
}
return RedirectToAction("InvalidUserName");
}
This creates an email that has a link to a Web API that accepts the token as the id that is passed in. When they click on the link it hits this method.
[AllowAnonymous]
public ActionResult ResetPasswordConfirmation(string Id)
{
ResetPasswordConfirmModel model = new ResetPasswordConfirmModel() { Token = Id };
return View(model);
}
This action gets the token from the query string and puts it in the ResetPasswordConfirmationModel that is passed to the view which allows the user to enter the new password. The new password is entered twice to make sure they entered it correctly, which is validate on the page. When they submit this information they are taken to the POST version of this action which actually resets the password.
[AllowAnonymous]
[HttpPost]
public ActionResult ResetPasswordConfirmation(ResetPasswordConfirmModel model)
{
if (WebSecurity.ResetPassword(model.Token, model.NewPassword))
{
return RedirectToAction("PasswordResetSuccess");
}
return RedirectToAction("PasswordResetFailure");
}
How to get the previous url using the Janrain RPX login?
I´m using Asp.Net MVC to request.
When an anonymous user browses to a secured view, asp.net will automatically redirect them to the SignIn page and place the ReturnUrl in the querystring. You can retain the ReturnUrl by appending it to the token_url used in the RPX login.
Here is the helper I created to generate the correct href for the RPX iframe on my SignIn page:
public static string RpxSignInUrl(this HtmlHelper htmlHelper)
{
string returnUrl = FormsAuthentication.GetRedirectUrl(
String.Empty, // userName (not used, but cannot be null)
false); // persistent cookie (also ignored)
string tokenUrl = "http://<your-domain>/Account/RpxResponse?ReturnUrl=" +
HttpUtility.UrlEncode(returnUrl);
string realm = "<your-app-id>.rpxnow.com";
string signInUrl = String.Format(
CultureInfo.InvariantCulture,
"http://{0}/openid/embed?token_url={1}",
realm,
HttpUtility.UrlEncode(tokenUrl));
return signInUrl;
}
After the user authenticates, RPX will call this url which now includes the original ReturnUrl. You can use FormsAuthentication.GetRedirectUrl again to retrieve the return url.
Note that its important to use this API rather than just getting the value from the Request.Querystring collection because it validates the return url, ensuring that its from the same domain. If the url is deemed unsafe, asp.net will fall back to the defaultUrl property specified on the forms element in your web.config.