When I use forms authentication and add html attributes to html.beginfom and routeValue object I get nullRefrenceException - asp.net-mvc-4

#using (Html.BeginForm("Login", "Account", new { returnUrl = #ViewContext.HttpContext.Request.UrlReferrer.PathAndQuery}, FormMethod.Post, new { #class = "loginForm" }))
{
#Html.ValidationSummary(false)
#Html.EditorForModel();
<input type="submit" value="Login" />
}
exception :
An exception of type 'System.NullReferenceException' occurred in
App_Web_qpa3itia.dll but was not handled in user code
Additional information: Object reference not set to an instance of an object.

UrlReferrer may be null. Need to handle that case.

Related

Blazor Server auth with jwt

I have been trying for a few days to do client-side persistence token authorization. The problem is that we essentially cannot use IJSRunrime when a user request comes in to check his authentication, because ServerRendering does not run before initialization on the client. I also tried to use cookies in order to save them in the request, but we cannot do this either due to the fact that the request has already been started, for example, in the same MVC, we can do this without problems.
Detailed errors:
IJSRunrime - "JavaScript interop calls cannot be issued at this time"
Cookie - "headers are read-only, response has already started"
I create this code for example and my question - How save token on the client side?
UPD: I don't want to reload SPA application, some examples do it and I can't use HttpContext.SignIn because have Cookie error.
#page "/SignIn"
<EditForm EditContext="#editContext" OnValidSubmit="SendRequestSubmit" class="auth__form">
<DataAnnotationsValidator />
<div class="auth__item">
<h4 class="auth__actions-title actions-title">Email<span>*</span></h4>
<InputText #bind-Value="signInRequest.Email" class="input-actions" />
</div>
<div class="auth__item">
<h4 class="auth__actions-title actions-title">Password<span>*</span></h4>
<InputText #bind-Value="signInRequest.Password" type="password" class="input-actions" />
</div>
<button class="auth__confirm actions-btn btn" disabled="#sendRequestLoading">
Sign In
</button>
</EditForm>
#code {
// it is code for example
private async void SendRequestSubmit()
{
var auth = accountSerrvice.auth(Email, Password);
if(auth == true && auth.Token != null)
{
// Save auth.Token in the client side
}
else
{
throw new Exception("Test error");
}
}
}

ASP.NET Core - use external provider but *keep* Default Identity database

I want the database that comes with the Default Identity provider in ASP.NET Core. However, I'd like users to login exclusively with their Microsoft account.
So at the moment, I have this in my user LoginDisplay.razor file:
<AuthorizeView>
<Authorized>
Hello, #context.User.Identity.Name!
Log out
</Authorized>
<NotAuthorized>
Register
Log in
</NotAuthorized>
</AuthorizeView>
When the user clicks "Log in" they're taken to the regular login form:
Here they can click on the "Microsoft Account" button. What I would like to do is skip the default login screen and go directly to the Microsoft Account workflow.
How would I do that?
Keeping the identity database offers me a couple of benefits:
I plan to add more data to the database - so it's handy if I can refer to accounts that exist in the same database
It's possible that I may need to give users access to the site that do not have a Microsoft account
Update
Based on feedback, I've implemented the following:
#inject Data.Services.AntiForgery antiforgery;
<form id="external-account" method="post" class="inline-block form-horizontal" action="/Identity/Account/ExternalLogin?returnUrl=%2F">
<button type="submit" name="provider" value="microsoft" title="Log in using your Microsoft Account account">Login</button>
<input name="__RequestVerificationToken" type="hidden" value="#antiforgery.Generate()">
</form>
And here's my utility class that I used to work around the anti-forgery request token (in Blazor):
public class AntiForgery
{
public IAntiforgery Antiforgery { get; private set; }
public IHttpContextAccessor Accessor { get; private set; }
public AntiForgery( IAntiforgery antiforgery, IHttpContextAccessor accessor )
{
Antiforgery = antiforgery;
Accessor = accessor;
}
public string Generate()
{
// Code stolen from:
// * https://stackoverflow.com/questions/45254196/asp-net-core-mvc-anti-forgery; and
// * https://stackoverflow.com/questions/53817373/how-do-i-access-httpcontext-in-server-side-blazor
return Antiforgery.GetAndStoreTokens( Accessor.HttpContext ).RequestToken;
}
}
For the utility class to work, the following was added to my Startup file:
services.AddSingleton<AntiForgery>();
services.AddHttpContextAccessor();
Well, you can simply just hide the login form itself, showing only the Microsoft Account button. However, it is not possible to send the user directly into that flow. It requires an initial post, which is going to require action on the user's part, i.e. clicking the button.
For what it's worth. If you have a "Login" type link, you can code this in the same way as the Microsoft Account button, so that it then initiates the flow when the user clicks "Login". However, you'll still need an actual login page to redirect to for authorization failures, and that would still require an explicit button press there.
You could directly pass the provider name Microsoft to your external login function using asp-route-provider.
For asp.net core 2.2+, Identity is scaffolded into identity area with Razor Pages.
1.Login link.
<a asp-area="Identity" asp-page="/Account/ExternalLogin" asp-page-handler="TestExternal" asp-route-provider="Microsoft">Log in</a>
2.ExternalLogin.cshtml.cs
public IActionResult OnGetTestExternalAsync(string provider, string returnUrl = null)
{
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
3.Startup.cs
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
//use your own Id and secret
microsoftOptions.ClientId = Configuration["Authentication:Microsoft:ClientId"];
microsoftOptions.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
});

How to use Basic authentication for web browser requests with ServiceStack?

I have a REST API built using ServiceStack. I am using BasicAuthentication without any issues when calling the REST APIs (I am registering the AuthFeature with the BasicAuthProvider).
Now I am trying to build some HTML management pages. These should also be authenticated.
The [Authenticate] attribute redirects to the /login page, so I created the following DTO and matching service to handle logins:
[DefaultView("Login")]
public class SiteLoginService : EnshareServiceBase
{
public object Get(SiteLoginRequest req)
{
return new SiteLoginRequest();
}
public object Post(SiteLoginRequest req)
{
//I am trying to use the registered IAuthProvider, which is the BasicAuthProvider
var authProvider = ResolveService<IAuthProvider>();
authProvider.Authenticate(this, EnshareSession,
new ServiceStack.ServiceInterface.Auth.Auth()
{
Password = req.Password,
UserName = req.UserName
});
return HttpResult.Redirect(req.Redirect);
}
}
[Route("/login")]
public class SiteLoginRequest : IReturn<SiteLoginRequest>
{
public string Redirect { get; set; }
public string Password { get; set; }
public string UserName { get; set; }
}
However, the BasicAuthProvider always throws HttpError: "Invalid BasicAuth credentials" when I fill in username and password on the Login view page and POST these to the SiteLoginService. It is probably because the web browser is not filling in the Basic auth header, but I do not know how to authenticate with filled in username and password.
How to properly authenticate site users against the AuthProvider which works with the existing REST API?
If you are passing the Username & Password as a post, then as you suspect you are not doing Basic Authentication.
This article explains how to do basic authentication with JavaScript. From the article:
function login() {
var username = document.getElementById(this.id + "-username").value;
var password = document.getElementById(this.id + "-password").value;
this.http.open("get", this.action, false, username, password);
this.http.send("");
if (http.status == 200) {
document.location = this.action;
} else {
alert("Incorrect username and/or password.");
}
return false;
}
ServiceStack also supports other forms of authentication including sending a username and password via a POST if that is what you want. If you explain your requirements we can give some recommendations.
I figured I need to include also the CredentialsAuthProvider in the AuthFeature, which will expose /auth/credentials service which I form post a form to.
//this inherits the BasicAuthProvider and is used to authenticate the REST API calls
var myCustomAuthProvider = new CustomAuthProvider(appSettings);
var credentialsProvider = new CredentialsAuthProvider(appSettings);
container.Register<IAuthProvider>(myCustomAuthProvider);
container.Register<CredentialsAuthProvider>(credentialsProvider);
var authFeature = new AuthFeature(() => new EnshareSession(new MongoTenantRepository()),
new IAuthProvider[] {
myCustomAuthProvider,
credentialsProvider
})
So I specified the action in my login form as /auth/credentials, while providing the required UserName and Password fields.
<form action="/auth/credentials" method="post">
<p class="entryfield">
#Html.LabelFor(m => m.UserName, "Login name:")
#Html.TextBoxFor(u => u.UserName)
</p>
<p class="entryfield">
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
</p>
<input class="formbutton" type="submit" value="Login" />
</form>
When the form is posted, it hits the authentication code flows properly (TryAuthenticate is called in my IUserAuthRepository and returns true).
Ultimately the request receives a 302 and my login form at /login is redisplayed.
HTTP/1.1 302 Found
Server: ASP.NET Development Server/10.0.0.0
Date: Wed, 30 Oct 2013 08:15:54 GMT
X-AspNet-Version: 4.0.30319
X-Powered-By: ServiceStack/3,969 Win32NT/.NET
Location: http://localhost:64944/login?redirect=%2fadmin
Set-Cookie: X-UAId=3; expires=Sun, 30-Oct-2033 08:15:54 GMT; path=/; HttpOnly
It is setting the session cookie (X-AUId) and the user is properly authenticated. Subsequent web browser requests to Services decorated with the Authenticate attribute succeed.
So the only missing part is how to ensure that the user is properly redirected after posting to /auth/credentials.
To ensure the redirection works, a quick look at the has shown that a Continue parameter is expected.
So this is how the login form needs to look like (I reused the Auth class from ServiceStack for the model):
#inherits ViewPage<ServiceStack.ServiceInterface.Auth.Auth>
#{
Layout = "AdminLayout";
}
<form action="/auth/credentials" method="post">
<p class="entryfield">
#Html.LabelFor(m => m.UserName, "Login name:")
#Html.TextBoxFor(u => u.UserName)
</p>
<p class="entryfield">
#Html.LabelFor(m => m.Password)
#Html.PasswordFor(m => m.Password)
</p>
#Html.HiddenFor(m => m.Continue)
<input type="submit" value="Login" />
</form>
The Continue property is populated in the service from the Redirect property of its model.

Using PUT verb in edit form using ASP.NET MVC 4 and Restful Routing .NET

I'm using the Restful Routing .NET NuGet package (https://github.com/stevehodgkiss/restful-routing) in my ASP.NET MVC 4 project. According to the documentation, I should be able to submit a form using the PUT verb to the Update action in a controller by placing the #HTML.PutOverrideTag() in the form. I have not been able to get this to work. Every time I submit the form I get a 404 Not Found error. Can anyone confirm whether they have been able to get this working properly? My code is below:
routeconfig.cs:
map.Resources<UsersController>();
views\users\edit.cshtml:
#using (Html.BeginForm("update", "users", new { id = Model.Id }))
{
#Html.PutOverrideTag()
<input id="user_submit" name="commit" type="submit" value="Update" />
}
UsersController.cs:
[Authorize]
[HttpGet]
public ActionResult Edit(int id)
{
var user = UserRepository.GetById(id);
return View(user);
}
[Authorize]
[HttpPut]
public ActionResult Update(int id, User user)
{
return View("edit", user);
}
Steps to recreate:
Browse to: /user/edit
Form displays
Press submit button
Receive 404 Not Found error
Try
#Html.HttpMethodOverride(HttpVerbs.Put)
instead of
#Html.PutOverrideTag()

Upload a file MVC 4 Web API .NET 4

I'm using the version of MVC that shipped with Visual Studio 2012 express. (Microsoft.AspNet.Mvc.4.0.20710.0)
I assume this is RTM version.
I've found plenty of examples online which all use this code:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
But this code always ends up in continueWith where t.IsFaulted == true. The exception reads:
Unexpected end of MIME multipart stream. MIME multipart message is not
complete.
Here is my client form. NOthing fancy, I want to do jquery form pluging for ajax upload, but I can't even get this way to work.
<form name="uploadForm" method="post" enctype="multipart/form-data" action="api/upload" >
<input type="file" />
<input type="submit" value="Upload" />
</form>
I've read that it is caused by the parser expecting /CR /LF at the end of each message, and that bug has been fixed in June.
What I cannot figure out is, if it was really fixed, why isn't it included this version of MVC 4? Why do so many examples on the internet tout that this code works when it does not in this version of MVC 4?
You are missing a name attribute on your file input.
<form name="uploadForm" method="post" enctype="multipart/form-data" action="api/upload" >
<input name="myFile" type="file" />
<input type="submit" value="Upload" />
</form>
Inputs without it will not get submitted by the browser. So your formdata is empty resulting in IsFaulted being asserted.