Redirect from API action to MVC action - asp.net-core

I am using ASP.NET Core RC1 and I have an API action which is being called by the client using Angular:
[HttpPost("account/signin")]
public async Task<IActionResult> SignIn([FromBody]SignInModel model) {
if (ModelState.IsValid) {
// Redirect to Home/Index here
} else {
return HttpBadRequest();
}
}
How to redirect from this action that is called by the client to the Home/Index action to load that page?

You'll need to handle the redirection in Angular, not on the server. Your sign in API should return an indication of success to Angular, then in your Angular controller that called the API, you'll need to check the response value and make the transition to the relevant page.
If you return a redirect response from the API, Angular will simply follow that redirect then receive the content of the Home/Index into its HTTP request object, which is unlikely to be what you want.
API method:
[HttpPost("account/signin")]
public async Task<IActionResult> SignIn([FromBody]SignInModel model) {
if (ModelState.IsValid) {
return new { Authenticated = true };
} else {
return HttpBadRequest();
}
}
Here's an example of the sort of handling code you might want in your Angualar controller (this assumes "/" is the URL for Home/Index):
$http.post(
"account/signin",
{ "Username": vm.username, "Password": vm.password }
).then(function (authResponse) {
if (authResponse.data.Authenticated) {
// Reload the page:
$window.location.href = "/";
} else {
vm.success = false;
vm.failMessage = "Login unsuccessful, please try again.";
}
}, function (errResponse) {
// Error case.
vm.failMessage = "Unable to process login - please try again."
});

Related

Vue app and .NET Core Web Api losing session

I hope this has not already been asked, I can't seem to find what I need. I have a VUE 3 app and am using a .NET Core Web API to retrieve data from a service. In the Vue app I make an axios call to log in the user
await axios({
method: 'post',
url: 'https://localhost:44345/api/Authentication/SignIn',
contentType: "application/json",
params: {
username: signInData.value.username,
password: signInData.value.password,
keepMeSignedIn: signInData.value.keepMeSignedIn
}
}).then(response => {
if (response.data.succeeded) {
console.log("Result: ", response.data.data);
}
else {
emit('handleServerSideValidationErrors', response);
}
This then calls my API where I call the service to sign in the user. Once I have verified the information and have the user data it is getting set in session.
public void Set<T>(string key, T value)
{
if (key.IsNullOrEmpty())
{
throw new Exception("The key parameter for SessionUtil.Set is required. It cannot be null/empty.");
}
else
{
this._validateSessionObjectVersion();
if (value == null)
{
Remove(key);
}
else
{
string json = JsonConvert.SerializeObject(value, Formatting.None, new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore });
_httpContextAccessor.HttpContext.Session.SetString(key, json);
}
}
}
The issue I am running into is, when I go to another page that needs to access this session it is null. The API calls this get method but is null.
public T Get<T>(string key)
{
T value = default(T);
if (key.IsNullOrEmpty())
{
return value;
}
if (_httpContextAccessor.HttpContext == null)
{
return value;
}
this._validateSessionObjectVersion();
string json = _httpContextAccessor.HttpContext.Session.GetString(key);
if (!json.IsNullOrEmpty())
{
value = JsonConvert.DeserializeObject<T>(json);
}
return value;
}
My Vue app is running on localhost:5001 while my API is running on localhost:44345. I do have a cors policy already in place which allows me to call the API but I don't see what I need to do in order to not lose session.
Turns out my issue was I had set the cookie option of SameSite to SameSiteMode.Lax. As soon as I changed it to SameSiteMode.None it was working for me.

Keycloak Log out from ASP.NET and ASP.NET Core

Currently I am able to login from ASP.NET and ASP.NET Core. However when logout from ASP.NET, my ASP.NET Core app doesn't logout as well.
Here is my ASP.NET logout code:
public ActionResult logout()
{
Request.GetOwinContext().Authentication.SignOut(HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes().Select(o => o.AuthenticationType).ToArray());
return RedirectToAction("About", "Home");
}
And my ASP.NET Core logout:
public IActionResult Logout()
{
return new SignOutResult(new[] { "OpenIdConnect", "Cookies" });
}
Unfortunately, if I logout from the ASP.NET app, my ASP.NET Core app doesn't logout automatically. Is it something wrong with my keycloak setting, or did I miss something in my code?
Go through the https://github.com/dotnet/aspnetcore/blob/4fa5a228cfeb52926b30a2741b99112a64454b36/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L312-L315
services.AddAuthentication(...)
.AddCookie("Cookies")
.AddOpenIdConnect("OpenIdConnect", options =>
{
...
options.Events.OnSignedOutCallbackRedirect += context =>
{
context.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
...
});
It is working for me. I used code similar to yours:
public IActionResult Logout()
{
return new SignOutResult(
new[] {
OpenIdConnectDefaults.AuthenticationScheme,
CookieAuthenticationDefaults.AuthenticationScheme
});
}
If you get invalid redirect error from keycloak, then you must also add Valid post logout redirect URIs to your Keycloak client settings. In your case, you have to add your_host/signout-callback-oidc
If you get an invalid or missing id_token_hint parameter, then make sure that tokens are being saved:
.AddOpenIdConnect(options =>
{
options.SaveTokens = true;
});

User.Identity.IsAuthenticated AND _signInManager.IsSignedIn(User) return always null / fasle IN MVC CORE 6 2022

I have a new asp.net MVC core 6 application .try to authenticate users ( not by using Identity scaffolding ) .. however the the SignInmanger is always return False
Login function
programe.cs
Full code snippet for login :
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginVM loginvm)
{ // this wil return view model
if (!ModelState.IsValid)
{
return View(loginvm);
}
var user = await _userManager.FindByEmailAsync(loginvm.Username);
if (user != null)
{
// if we have user let us check the password
var checkpsssword = await _userManager.CheckPasswordAsync(user, loginvm.Password);
if (checkpsssword)
{
var letUserLoginIn = await _signInManager.PasswordSignInAsync(user, loginvm.Password, false, false);
if (letUserLoginIn.Succeeded)
{
var tempo = User.Identity.IsAuthenticated;
var isok = _signInManager.IsSignedIn(User);
ViewBag.tempo=tempo;
ViewBag.isok = isok;
return RedirectToAction("index", "Movie");
}
ModelState.AddModelError("Error","can login innnnn");
TempData["Error"] = "Password is not correct! !";
return View(loginvm);
}
else
{
// password wrong
TempData["Error"] = "Password is not correct! !";
}
}
TempData["Error"] = "no user found ya mozznoz!";
return View(loginvm);//STRONGLY TYPED VIEW
}
One part #Kevin have mentioned above, and another part was the missing of authentication mechanism register.
It should be something like builder.Services.AddAuthentication(opts => opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
AddAuthentication part add all the necessary middlewares and config to setup authentication process. Here, we specify DefaultScheme as CookieAuthenticationDefaults.AuthenticationScheme
AddCookie tell asp.net Core that we want to store the login information in cookie, therefore, a response that tell client to save a cookie with pre-defined information was sent(and the name for that authentication mechanism of choice was default to CookieAuthenticationDefaults.AuthenticationScheme).
For every subsequent requests, the cookie was included then server know, we already logged in

Axios Interceptor is not working after page refresh (Vue.js)

I am working on a simple crud-application, which is developed with spring,vue.js and h2 database.
I am almost done but unfortunately, I have some problems with the authentication. When I type all required credentials, the login will succeed and I will be redirected to page with a meal table, which displays the data from an API.
When I click on other pages, suddenly the console shows me an error message:
[1]: https://i.stack.imgur.com/zQBw0.png
After a while, I have replaced all "href" to "to". Finally, the navigation through the web application worked and also the access to the data was not denied. Unfortunately, after a page refresh, the access was denied and I received the above error message again.
I have checked the session storage in the Browser if my user account is still saved after a page refresh and this was the case.
I don't know, what the exact error is.
I appreciate your help :)
Backend:
SpringSecurityConfigurationAuthentication.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SpringSecurityConfigurationAuthentication extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated()
.and()
//.formLogin().and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.inMemoryAuthentication()
.withUser("admin")
.password("{noop}admin")
.roles("ADMIN")
.and()
.withUser("user")
.password("{noop}user")
.roles("USER");
}
}
AuthenticationBeanController.java
#RestController
#RequestMapping(value="/basicauth")
#CrossOrigin(origins = "*", allowedHeaders = "*")
public class AuthenticationBeanController {
#GetMapping
public AuthenticationBean authenticating() {
return new AuthenticationBean("You are authenticated");
}
}
Frontend Vue.js :
http-common.js:
import axios from 'axios'
export default axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-type": "application/json",
}
})
AuthenticationService.js
import http from '../http-common'
class AuthenticationService {
registerSuccesfulLogin(username, password) {
this.setupAxiosInterceptors(this.createBasicAuthToken(username, password))
}
startAuthentication(username, password) {
return http.get('/basicauth', {headers: {authorization: this.createBasicAuthToken(username, password)}});
}
createBasicAuthToken(username, password) {
let userToken = 'Basic ' + window.btoa(username + ":" + password);
sessionStorage.setItem('authenticatedUser', username)
return userToken
}
logout() {
sessionStorage.removeItem('authenticatedUser');
}
isUserLoggedIn() {
let user = sessionStorage.getItem('authenticatedUser');
if (user === null) return false
return true
}
setupAxiosInterceptors(userToken) {
http.interceptors.request.use(
(config) => {
if (this.isUserLoggedIn()) {
config.headers.Authorization = userToken
}
return config
}
)
}
}
export default new AuthenticationService();
MealDataService.js
import http from '../http-common'
class MealDataService {
retrieveAllMeals() {
return http.get('/meal');
}
deleteMealById(id) {
return http.delete(`/meal/${id}`);
}
getMealById(id) {
return http.get(`/meal/${id}`);
}
updateMealById(data) {
return http.put('/meal', data);
}
addMealById(data) {
return http.post('/meal', data);
}
}
export default new MealDataService();
Possible lead :
Since your app is a single page client side app, without a proper server configuration, the users will get an error (404 if not found or authentication error if protected) if they access any other route different than home directly in their browser (or by refreshing).
Look at your web server configuration and see if there is any configuration to handle this behavior HTML5 history mode.

Login page redirects back only to a naked partialview

I use jquery ajax to call (get) an action which renders a partialview for creating(inputting) a new product. The content of this partialview is inserted into a vid tag in a full page which has a base _layout. The ajax code below:
$("#Create").on('click', function (e) {
//debugger;
e.preventDefault();
$.ajax({
type: "GET",
data: { returnUrl: String(window.location) }, <-- passing in the Url
url: "/CreateGroup/CreateGroup",
dataType: 'html',
success: function (data) {
$("#group-list").html(data);
},
});
})
.....
<div id="group-list">
#Html.Partial("PagedGroupList")
</div>
The action:
// GET: /Create/
[Authorize]
public PartialViewResult CreateGroup(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
CreateGroupModel cgm = new CreateGroupModel();
cgm.ReturnUrl = returnUrl;
cgm.group = new Static_Group();
return PartialView("CreateGroup", cgm);
}
Now if I login first before clicking CREATE button, eveerything is fine as expected. The problem is when CREATE button is clicked without login first. Due to [Authorize], the login page will come first and after login, it is expected to redirect back to my CREATE partialview inside the full page together. The problem is that the login page redirects back to a NAKED create partialview without any of its parent view's elements. If the CREATE page is NOT a partialview, login page redirects back to the full page perfectly.
I use MVC4 defafult login. I tried to make the login page into a ajax form submit and use OnSuccess to call document.location in both the login page and the create partialview, but I found it is an issue of returnUrl of the partialview. I also tried Request.UrlReferrer.AbsoluteUri, but it gives the same URL which is "/CreateGroup/CreateGroup?returnUrl=http%3A%2F%2Fwww.dev.com%3A22531%2F" or in full: http://www.dev.com:22531/CreateGroup/CreateGroup?returnUrl=http://www.dev.com:22531/. If you put the Url into a browser, it also displays the pure partialview.
I believe I must have missed something obvious coz this should be a common scenario but I could not find any threads from googling. Is there a URL which displays a partialview inside its parent view together? if not, then possible to redirect back to a previous view from a partialview?
Looks there is no URL for a partialview with its host page. The solution I can think of at the moment is get login redirect to the host page and pass in the partialview name in Model or viewbag or session temp to render the whole page again with the right partialview in it.
Here is how I work around this issue. Don't feel very comfortable with it. If anyone has a better solution, please help.
I added two string vars in LoginModel:
public string RetUrl { get; set; }
public string UrlReferrer { get; set; }
In get Login action:
public PartialViewResult Login(string returnUrl)
{
ViewBag.ReturnUrl = Request.UrlReferrer.AbsolutePath;
LoginModel lgm = new LoginModel();
lgm.UrlReferrer = Request.UrlReferrer.AbsoluteUri;
lgm.RetUrl = returnUrl;
return PartialView(lgm);
}
In post Login action:
public ActionResult Login(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
model.IsLoggedIn = true;
if (string.IsNullOrEmpty(model.RetUrl))
{
model.RetUrl = Url.Action("Index", "Home");
}
}
else
{
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
return PartialView("Login", model);
}
in Login.cshtml: put two hidden fields to retain Model preset value:
#Html.HiddenFor(m => m.RetUrl)
#Html.HiddenFor(m => m.UrlReferrer)
In ajax OnSuccess event handler, append a CallPartial querystring in the end of returnUrl and redirect to this returnUrl:
<script type="text/javascript">
function logInComplete() {
//debugger;
if ('#Model.IsLoggedIn' == 'True' && '#Model.UrlReferrer' != '') {
//debugger;
var returnUrl = '#Model.UrlReferrer';
if ('#Model.RetUrl' != '#Model.UrlReferrer') {
if (returnUrl.indexOf('?') == -1) {
returnUrl = returnUrl + "?CallPartial=True";
}
else {
returnUrl = returnUrl + "&CallPartial=True";
}
}
document.location = returnUrl;
}
}
Now back to the CREATE partialview host page:
$('.ifCallPartial').each(function () {
//debugger;
if ('#Request["CallPartial"]' == "True") {
$(document).ready(function () {
CallCreate();
});
}
});
function CallCreate() {
//debugger;
//e.preventDefault();
var returl = String(window.location);
var n = returl.indexOf("?CallPartial");
if (n >= 1) {
returl = returl.substring(0, n);
}
else {
n = returl.indexOf("CallPartial");
if (n >= 1) {
returl = returl.substring(0, n);
}
}
$.ajax({
type: "GET",
data: { returnUrl: returl },
url: "/CreateGroup/CreateGroup",
dataType: 'html',
success: function (data) {
$("#group-list").html(data);
},
error: function (xhr, status, error) {
alert(error);
}
});
}