spring security & axios - email: "NONE_PROVIDED" - spring

Logging in via postman works fine ONLY when using application/x-www-form-urlencoded
Trying to do the same request with raw-json doesn't work.
Anyway I'm trying to implement the login to my react app. When i have the request set as:
const response = await Axios.post(`${window.ipAddress.ip}/login`,
JSON.stringify({ email: email.toLowerCase(), password: password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
From this in springboot the UserService class gets called which shows:
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = findUserByEmail(email);
if ( user != null){
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getName()));});
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
else{ throw new EntityNotFoundException("Entity not found"); }
}
It then shows email as "NONE_PROVIDED" and ultimately failing.
The second issue is I don't actually have a /login route in controller code when searching through all files so I'm unsure at which point it actually calls this method through springboot.
The only changes I made from the default spring-security implementation is the use of my own class where I use "email" in place of "userName".
Any suggestions are welcome.
Edit:
the working login function via postman

Springboot was expecting encoded form which is different from my other requests , this answer shows how to properly outline x-www-for-urlencoded
axios post request to send form data
having the request as:
var bodyFormData = new FormData();
bodyFormData.append('email', email.toLowerCase());
bodyFormData.append('password', password);
const response = await Axios({
method: "post",
url: `${window.ipAddress.ip}/login`,
data: bodyFormData,
headers: { "Content-Type": "multipart/form-data" },
})

Related

Extra information gets lost when updating user context

I've been fighting with this issue for days now and I just can't solve it. My app is built on React and Django Rest Framework. I'm authenticating users with JWT - when the user logs into the app, the React Auth context gets updated with some info about the tokens and I include some extra information in the context (namely the user email and some profile information) so that I have it easily accessible.
How I am doing this is by overwriting TokenObtainPairSerializer from simplejwt:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
return token
On the frontend in my AuthContext.js:
const loginUser = async (email, password, firstLogin = false) => {
const response = await fetch(`${baseUrl}users/token/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (response.status === 200) {
setAuthTokens(data);
setUser(jwt_decode(data.access));
localStorage.setItem("authTokens", JSON.stringify(data));
if (firstLogin) {
history.push("/profile");
} else {
history.push("/");
}
} else {
return response;
}
};
Up to this point it works perfectly fine and my ReactDevTools show me that the AuthContext has all the data:
Now to the issue - once the access token has expired, the next API call the user makes gets intercepted to update the token. I do this in my axiosInstance:
const useAxios = () => {
const { authTokens, setUser, setAuthTokens } = useContext(AuthContext);
const csrfToken = getCookie("csrftoken");
const axiosInstance = axios.create({
baseURL,
headers: {
Authorization: `Bearer ${authTokens?.access}`,
"X-CSRFToken": csrfToken,
"Content-Type": "application/json",
},
});
axiosInstance.interceptors.request.use(async (req) => {
const user = jwt_decode(authTokens.access);
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
if (!isExpired) return req;
const response = await axios.post(`${baseURL}users/token/refresh/`, {
refresh: authTokens.refresh,
});
// need to add user info to context here
localStorage.setItem("authTokens", JSON.stringify(response.data));
setAuthTokens(response.data);
setUser(jwt_decode(response.data.access));
req.headers.Authorization = `Bearer ${response.data.access}`;
return req;
});
return axiosInstance;
};
export default useAxios;
But the extra information is not there. I tried to overwrite the TokenRefreshSerializer from jwt the same way as I did it with the TokenObtainPairSerializer but it just doesn't add the information
class MyTokenRefreshSerializer(TokenRefreshSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
print(token)
return token
It doesn't even print the token in my console but I have no clue what else I should try here.
Before anyone asks, yes I specified that the TokenRefreshView should use the custom serializer.
class MyTokenRefreshView(TokenRefreshView):
serializer_class = MyTokenRefreshSerializer
However, after a while of being logged into the application, the email and information key value pairs disappear from the context.
Any idea about how this can be solved will be much appreciated!

Axios Post in react js is giving error No 'Access-Control-Allow-Origin' header is present on the requested resource

I am trying to do create Login page using react in my web application with spring boot in backend. I am using spring security JDBC Authentication for login. I am trying to convert my JSP pages to React. Login is working fine with JSP and spring boot. Now i am trying to create same page with react. but when i post using axios post i am getiing error
Access to XMLHttpRequest at 'http://localhost:8080/onlineshopping/login' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
this is axios Post
export const Login = (username, password) => async dispatch => {
console.log(password)
let params = {
username: username,
password: password
}
const res = await axios.post("http://localhost:8080/onlineshopping/login", {params});
dispatch({
type: Login,
payload: res.data
});
};
SecurityConfig.java
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.usersByUsernameQuery("select email, password, enabled from user_detail where email = ?")
.authoritiesByUsernameQuery("select email, role from user_detail where email = ?")
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
}
Pagecontroller.java
#RestController
#CrossOrigin
public class PageController {
#RequestMapping("/login")
public Map<String, Object> login(
#RequestParam(name = "error", required = false) String error,
#RequestParam(name = "logout", required = false) String logout) {
Map<String, Object> map = new HashMap<String, Object>();
System.out.println("Login");
map.put("title", "Login");
if (error != null) {
map.put("message", "Username and Password is invalid!");
}
if (logout != null) {
map.put("logout", "You have logged out successfully!");
}
return map;
}
}
Please tell me why i am getting this error and how to solve it.
You have to add proxy address to your package.json file, e.g.:
},
"proxy": "http://localhost:8080",
"devDependencies": {
Next, you just add all the which is after the localhost, i.e.
axios.get("/onlineshopping/login")
After adding CORS filter configuration in spring boot and content type to application/x-www-form-urlencoded in axios request my problem solved.
export const addProjectTask = (username,password, history) => async dispatch => {
axios.post('http://localhost:8080/onlineshopping/login',
Qs.stringify({
username: username,
password: password
}), {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}})
.then(function (response) {
console.log(response);
history.push("/");
})
.catch(function (error) {
console.log(error);
});
};
You have to create a proxy for API calls.
Here, proxy uses url pattern to match to the api calls and redirecting them to the corresponding server.
Try with the following:
Install http-proxy-middleware
npm install http-proxy-middleware --save
create src/setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(proxy('/api', { target: 'http://localhost:8080/' }));
};
Then run your local Dev server

Angular POST to Spring Security JWT login always returns Unauthorized

I've set-up a stateless Spring Security application that uses JWT tokens for authentication. I've launched the application and tried to login over Postman with credentials I inserted into the database using a POST method (I've tried both form data and x-www-form-urlencoded) and it works successfully, it returns the JWT token and the expiration time. However, when I try to post to the same URL over Angular I always get Unauthorized; Bad credentials no matter if I use correct username-password combo.
I've tried POSTing 3 ways:
1
#Injectable()
export class AuthService {
constructor(private http: HttpClient) {
}
login(username: string, password: string): Observable<any> {
return this.http.post(environment.api + '/auth/login', {
'username': username,
'password': password
});
}
}
2
login(username: string, password: string): Observable<any> {
let body = 'username=' + username + '&password=' + password;
let headers = new HttpHeaders();
headers.set('Content-Type', 'x-www-form-urlencoded');
return this.http.post(environment.api + '/auth/login', body, {
headers: headers
});
}
3
login(username: string, password: string): Observable<any> {
let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);
return this.http.post(environment.api + '/auth/login', body);
}
But every single method keeps returning 401: Unauthorized, as if my username and password are always wrong (and I keep trying with correct ones that work on Postman). What am I doing wrong?
Here I share a piece of my Angulare login service that sends a login request to a Spring Login Controller running under localhost 8181. I hope this will help.
import { Injectable } from '#angular/core';
import {Http, Headers} from '#angular/http';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class LoginService {
constructor(private http: Http) { }
sendCredential(username: string, password: string) {
let url = "http://localhost:8181/token";
let encodedCredentials = btoa(username+":"+password);
let basicHeader = "Basic "+encodedCredentials;
let headers = new Headers ({
'Content-Type' : 'application/x-www-form-urlencoded',
'Authorization' : basicHeader
});
return this.http.get(url, {headers: headers});
}
Found the problem, it was in the HttpClient itself not sending post data correctly for unknown reasons. Tried it with the Http service and it works as it should.

I can't consume microservice Spring

I'm learning java Spring and I want to consume one microservice so I created a form in HTML and I try to send the user and password with axios
var helloWorld = new Vue({
el: '#vue-app',
data:
{
user: "user",
username : "",
password : ""
},
methods:
{
enviar: function()
{
axios.post('/user/login', {
user: this.username,
password: this.password
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
});
and I try to get the information
#Controller("/user")
public class UserController {
private final Log log = LogFactory.getLog(UserController.class);
#PostMapping("/login")
public boolean login(#RequestParam("user") String user, #RequestParam("password") String password)
{
log.info("user: " + user + " password: " + password);
return user.equals("hitzu") && password.equals("250693");
}
}`
But when I try to run the code I get error 404 and I try to set the URL in Postman
http://localhost:8080/user/login?user=hitzu&password=250693
but get the same error.
You are probably getting 404 because your controller thinks it should bind a View with the data it fetches.
Tell it not to bind a View essentially making it a REST endpoint and directly write into the HTTP Response Body by annotating your method with #ResponseBody.
Some further info on #ResponseBody from the Documentation

Forms validation in Nancy not working with AJAX login requests

I'm trying to implement an extremely simple spike using Nancy as an alternative to ASP.NET MVC.
It should take a username (no password) and provide meaningful error messages on the same login page without requiring a refresh. If login was successful, the response includes the URL to navigate to.
The POCO for the response looks like this:
public class LoginResponseModel
{
public bool IsSuccess { get; set; }
public string RedirectUrl { get; set; }
public string ErrorMessage { get; set; }
}
The JS handler for the login request:
$.ajax({
url: '/login',
type: "POST",
data: { UserName: username }
}).done(function (response) {
if (response.IsSuccess) {
showSuccess();
document.location.href = response.RedirectUrl;
return;
}
showError(response.ErrorMessage);
}).fail(function (msg) {
showError("Unable to process login request: " + msg.statusText);
});
The problem I'm having is with Nancy's Forms-based authentication. I've walked through half a dozen different tutorials which all more or less do the same thing, as well as gone over the Nancy authentication demos. The one thing they all have in common is that they rely on the LoginAndRedirect extension method. I don't want to return a redirect. I want to return a result of the login attempt and let the client handle the navigation.
The IUserMapper implementation I'm using:
public class UserMapper : IUserMapper
{
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
// Don't care who at this point, just want ANY user...
return AuthenticatedUser {UserName = "admin"};
}
}
The relevant part of my LoginModule action:
var result = _userMapper.ValidateUser(input.AccessCode);
if (result.Guid != null) this.Login(UserMapper.GUID_ADMIN, expiry);
return Response.AsJson(result.Response);
but for subsequent requests Context.CurrentUser is always null.
If I add the following method to the Nancy.Demo.Authentication.Forms sample it reproduces the behaviour I'm seeing in my own project, leading me to believe LoginWithoutRedirect doesn't work how I expected.
Get["/login/{name}"] = x =>
{
Guid? userGuid = UserDatabase.ValidateUser(x.Name, "password");
this.LoginWithoutRedirect(userGuid.Value, DateTime.Now.AddYears(2));
return "Logged in as " + x.Name + " now <a href='~/secure'>see if it worked</a>";
};
The problem turns out to be that Context.CurrentUser with FormsAuthentication is dependent upon a cookie which isn't set if you don't return the NancyModule.Login() response.
var result = _userMapper.ValidateUser(input.AccessCode);
if (result.IsSuccess) {
this.LoginWithoutRedirect(result.Guid);
}
return Response.AsJson(result);
In this example, the LoginWithoutRedirect call returns a Response object with the cookie set. To handle this in an Ajax scenario I've had to add a AuthToken property to the LoginAjaxResponse class, then pass the cookie like so:
var result = _userMapper.ValidateUser(input.AccessCode);
var response = Response.AsJson(result);
if (result.IsSuccess) {
var authResult = this.LoginWithoutRedirect(result.Guid);
result.AuthToken = authResult.Cookies[0].Value;
}
return Response.AsJson(result);
On the client, the Ajax response handler changes to (assuming use of jQuery cookie plugin:
$.ajax({
url: '/login',
type: "POST",
data: { UserName: username }
}).done(function (response) {
if (response.IsSuccess) {
showSuccess();
$.cookie("_ncfa", response.AuthToken); // <-- the magic happens here
document.location.href = response.RedirectUrl;
return;
}
showError(response.ErrorMessage);
}).fail(function (msg) {
showError("Unable to process login request: " + msg.statusText);
});
The AuthToken is the GUID which has been encrypted and base64-encoded. Subsequent requests with this.RequiresAuthentication() enabled will first check for this auth token cookie.
If no "_ncfa" cookie is present,the UserMapper's GetUserFromIdentifier() is never called.
If the value in Context.Request.Cookies["_ncfa"] does not result in a valid GUID when base64-decoded and decrypted, GetUserFromIdentifier() is never called.
If GetUserFromIdentifier() isn't called, Context.CurrentUser is never set.
If you want the source for a working example it's on GitHub.
LoginAndRedirect is only one option, there are equivalent methods for not redirecting (LoginWithoutRedirect), or one that picks up on whether it's an AJAX request and handles it appropriately (Login). The same applies to logging out.
This is all covered, in detail, in the documentation.

Resources