Backend Create user with signup JWT - spring-boot

I'm trying to create a restful web application with Springboot and Gradle. I use JWT for authentication. When a user logged the backend creates a user token for the user. this token he uses whenever he opens another page to retrieve his data.
What I'm trying to do is making a signup page, but the problem is that I cant send information to my backend without a Bearer token. How do I send a post method to create a user without authentication on this one single POST operation?
To generate a token for a user that exists
#PostMapping(value = "${jwt.get.token.uri}")
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtTokenResponse(token));
}
What I want to call to create a user in my database:
(want to do this POST without authorization)
#PostMapping(value = "/signup")
public ResponseEntity<User> createUser(#RequestBody User user){
HBMUserService HBMuserService = new HBMUserService();
User createdUser = HBMuserService.saveUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.OK);
}
My application properties:
jwt.signing.key.secret=SecretKey
jwt.get.token.uri=/authenticate
jwt.refresh.token.uri=/refresh
jwt.http.request.header=Authorization
jwt.token.expiration.in.seconds=604800

I Found out where my JWT Ignores my Auth path for login and added my signup path there as well.
.antMatchers(
HttpMethod.POST,
authenticationPath,
"/signup"
)

Related

Generate JWT OAuth2 Access token without password

My Spring Boot authorization service is able to create JWT tokens for users with correct credentials. It shall also be possible to get a token via social login (Facebook in this case).
The Facebook authentication already works and I find myself redirected to the following endpoint after I get the users's Facebook data.
I could create the JWT token completely from scratch, but that is not what I want. I want to use the already configured (with key pair and so on) TokenServices from my authentication server.
The only way I found was via TokenEndpoint. The problem is that I need the user's password, which I don't have and shouldn't have at this point.
How can I generate the token from what I already configured?
This endpoint is where I end up after Facebook's redirect:
#GetMapping("/loginSuccess")
fun getLoginInfo(authentication: OAuth2AuthenticationToken): ResponseEntity<OAuth2AccessToken> {
val client = authorizedClientService.loadAuthorizedClient<OAuth2AuthorizedClient>(authentication.authorizedClientRegistrationId, authentication.name)
val userInfoEndpointUri = client.clientRegistration.providerDetails.userInfoEndpoint.uri
if (!StringUtils.isEmpty(userInfoEndpointUri)) {
val restTemplate = RestTemplate()
val headers = HttpHeaders()
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + client.accessToken.tokenValue)
val entity = HttpEntity("", headers)
val response = restTemplate.exchange(userInfoEndpointUri, HttpMethod.GET, entity, Map::class.java)
// At this point I have the email address of the user and I am able to
// map it to my own User Entity
// This is where I would like to create a token and return it
// However, the following generation process requires the user's password
return authService.generateToken((response.body as Map<*, *>)["email"] as String)
}
throw AuthenticationException("Error")
}
I would like to use getAccessToken(OAuth2Authentication authentication) in JwtTokenStore but its implementation returns null:
#Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
// We don't want to accidentally issue a token, and we have no way to
// reconstruct the refresh token
return null;
}

Spring OAuth2.0: Getting User Roles based on ClientId (Authorization Code Grant Type)

I have a setup of spring boot OAuth for AuthServer and it is resposible for serving a number of few resource server for authentication using spring-security-jwt.
My problem is while authenticating I need to load the roles of a user but specific to the clientId.
eg: If user1 have roles ROLE_A, ROLE_B for client1 and ROLE_C, ROLE_D for client2, then when the user logins either using client1 or client2 he is able to see all the four roles ie. ROLE_A, ROLE_B, ROLE_C, ROLE_D because I am getting roles based on username.
If I need to have a role based on the client then I need clientId.
FYI,
I am using the authorization code flow for authentication.
I have seen similar question but that is based on password grant but I am trying on authorization code flow and that solution doesn't work for me.
Password grant question link
Below is my code where I need clientId
MyAuthenticationProvider.java
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException {
String userName = ((String) authentication.getPrincipal()).toLowerCase();
String password = (String) authentication.getCredentials();
String clientId = ? // how to get it
....
}
}
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) {
String clientId = ? // how to get it
....
}
}
You probably need to see OAuth2Authentication in Spring-security. When your client is authenticated by oauth2, then your "authentication" is actually instance of OAuth2Authentication that eventually implements Authentication.
If you see the implementation of OAuth2Authentication, it's done as below;
public Object getPrincipal() {
return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication
.getPrincipal();
}
so if request included "clientId', then you should be able to get clientId by calling getPrincipal() and typecasting to String as long as your request didn't include user authentication.
For your 2nd case, username is actually considered as clientId. You need to call in-memory, RDBMS, or whatever implementation that has clientId stored and returns ClientDetails. You'll be able to have some idea by looking into Spring security's ClientDetailsUserDetailsService class.
Since I didn't get any appropriate solution for my question, I am posting the solution that I used after digging source code and research.
MyJwtAccessTokenConverter.java (Extend JwtAccessTokenConverter and implement enhance method)
public class OAuthServerJwtAccessTokenConverter extends JwtAccessTokenConverter {
....
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String clientId = authentication.getOAuth2Request().getClientId();
// principal can be string or UserDetails based on whether we are generating access token or refreshing access token
Object principal = authentication.getUserAuthentication().getPrincipal();
....
}
....
}
Info:
In enhance method, we will get clientId from authentication.getOAuth2Request() and userDetails/user_name from authentication.getUserAuthentication().
Along with JwtAccessTokenConverter, AuthenticationProvider and UserDetailsService are required for authentication in generating access token step and refresh token step respectively.
get authorization header from request then parse from base64 to get the client-id.
something like this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes())
.getRequest();
String authHeader = request
.getHeader("Authorization");

How to validate facebook authorization token and sign up user using Spring(java)

I am developing an app whose frontend is written using React.js and the backend REST API is written using the Spring framework. I wanted to add social logins to my website, so after days of googling and research, I understood that OAuth2 is the solution. I came to know that the frontend should handle getting the authorization token from the Resource Server(Facebook here) and my backend(java) should validate that token and connect with Facebook to get an access token. Then that access token should be stored in my database along with the user details(e.g email).
Here is my requirement, once the user clicks on the "Continue with Facebook" button, my app should create there account in my own database using details - email and Name(the signup feature). And later whenever they click on this button again, they will be logged in not sign up. The way other websites handle it.
As of now, I have the button working in my app, which brings me the authorization token from Facebook.
Can someone please guide me the path I should follow here.
Also, any special attention to some error handling I should follow.
Here's the general approach using Spring Boot as a REST API backed by Spring Data JPA and Spring Security that works for iOS and ember.js together. There's probably libraries and what not that you can use but I'm just going to outline the fundamental flow.
Your user object needs a one to one mapping to a facebook account. Best practice would involve encrypting the authToken before storing in the DB
#Entity
class FacebookAccount {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
Long id
String facebookUserId
String authToken
#OneToOne
#JoinColumn(name="user_id")
User user
}
#Entity
class User{
...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
FacebookAccount facebookAccount
}
Use the facebook Javascript SDK to get a User Access Token and the User's Facebook User ID. You'll get a response back from facebook in your react app that looks like this in the successful case:
{
status: 'connected',
authResponse: {
accessToken: '...',
expiresIn:'...',
reauthorize_required_in:'...'
signedRequest:'...',
userID:'...'
}
}
Hit some login endpoint with the info received in step 2 like /login/facebook. I cannot predict how your app is structured. In my app, this code is handled by my Authentication Filter that implements GenericFilterBean. I pass a header X-Auth-Facebook with the token.
Verify the token. I'm doing this in a class that implements AuthenticationProvider within the Authentication authenticate(Authentication authentication) throws AuthenticationException method. This class will need your App's Access Token accessToken and the user's Token userAccessToken:
URIBuilder builder = URIBuilder.fromUri(String.format("%s/debug_token", "https://graph.facebook.com"))
builder.queryParam("access_token", accessToken)
builder.queryParam("input_token", userAccessToken)
URI uri = builder.build()
RestTemplate restTemplate = new RestTemplate()
JsonNode resp = null
try {
resp = restTemplate.getForObject(uri, JsonNode.class)
} catch (HttpClientErrorException e) {
throw new AuthenticationServiceException("Error requesting facebook debug_token", e)
}
Boolean isValid = resp.path("data").findValue("is_valid").asBoolean()
if (!isValid)
throw new BadCredentialsException("Token not valid")
String fbookUserId = resp.path("data").findValue("user_id").textValue()
if (!fbookUserId)
throw new AuthenticationServiceException("Unable to read user_id from facebook debug_token response")
// spring data repository that finds the FacebookAccount by facebook user id
FacebookAccount fbookAcct = facebookAccountRepository.findByFacebookUserId(fbookUserId)
if(!fbookAcct){
// create your user here
// save the facebook account as well
} else{
// update the existing users token
fbookAcct.authToken = userAccessToken
facebookAccountRepository.save(fbookAcct)
}
// finish the necessary steps in creating a valid Authentication
I, personally, then create a token that my client's use when accessing my API (rather than have them continue to pass the facebook token with all requests).
I also need more user provided information to create the user (a chosen username, agreeing to terms and conditions, etc). So my actual implementation throws an EntityNotFoundException instead of creating the user, which my clients then use to pop up a registration form that provides only the fields I cannot get from facebook. On submit of this from the client, I hit my /signup/facebook endpoint with the facebook token and what's needed to create my user. I fetch the profile from facebook and create the user (automatically logging them in the process).
Edit: If you want to use Spring 0Auth, you could follow the example for creating a Spring 2 Oauth Rest Template
#Bean
public OAuth2ProtectedResourceDetails facebook() {
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setId("facebook");
details.setClientId("233668646673605");
details.setClientSecret("33b17e044ee6a4fa383f46ec6e28ea1d");
details.setAccessTokenUri("https://graph.facebook.com/oauth/access_token");
details.setUserAuthorizationUri("https://www.facebook.com/dialog/oauth");
details.setTokenName("oauth_token");
details.setAuthenticationScheme(AuthenticationScheme.query);
details.setClientAuthenticationScheme(AuthenticationScheme.form);
return details;
}
#Bean
public OAuth2RestTemplate facebookRestTemplate(OAuth2ClientContext clientContext) {
OAuth2RestTemplate template = new OAuth2RestTemplate(facebook(), clientContext);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,
MediaType.valueOf("text/javascript")));
template.setMessageConverters(Arrays.<HttpMessageConverter<?>> asList(converter));
return template;
}
and then in use:
public String photos(Model model) throws Exception {
ObjectNode result = facebookRestTemplate
.getForObject("https://graph.facebook.com/me/friends", ObjectNode.class);
ArrayNode data = (ArrayNode) result.get("data");
ArrayList<String> friends = new ArrayList<String>();
for (JsonNode dataNode : data) {
friends.add(dataNode.get("name").asText());
}
model.addAttribute("friends", friends);
return "facebook";
}
I took the above request for friends from the project. it shouldn't be hard to tailor the above code I showed with debug_token to use the Spring OAuth rest template. Hope this helps :)

Spring-boot authentication

I have few Spring-boot controller classes to expose few rest web-services. Whenever some user tries to access any of those services, I need to invoke an web-service to check whether the user (user id will be passed as RequestHeader) is authorized or not. If not authorised, need to display an error page (freemarker template) to the user.
I don't want to write a method which will invoke the authentication webservice and call that from each controller methods and throw an exception and redirect the user to the access denied error page using #ControllerAdvice as here I have to call the method from all controller methods.
I'm not sure whether I can use WebSecurityConfigurerAdapter/AuthenticationManagerBuilder to call the webservice and do the validation.
I'm looking for some solution where I would write an interceptor and spring-boot will invoke the webservice before calling the controller classes and will be able to redirect to the error page, if validation fails.
As a recommendation, take a few minutes for reading about Spring Security (https://projects.spring.io/spring-security/), you must configure it and probably you will spend more time than expected, anyway you have so much more profits than make security by ourself.
Benefits are things like:
#PreAuthorize("hasRole('ROLE_USER')")
On every place you can get the user logged through the SecurityContext with something like:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
The way SpringSecurity authenticate users is with JWT (JsonWebToken) this is a really nice way because you can pass and retrieve all information you want:
public class CustomTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
User user = (User) authentication.getPrincipal();
final Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("customInfo", "some_stuff_here");
additionalInfo.put("authorities", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
And you can forget every possible problem (bad authentication, phishing, xss or csrf..) because it works with public/private key and secrets, so anyone can create a token.

Spring Boot - displaying the OTP page, restrict access to other pages using their URLs (after successful authentication on the login page)

I have created a Spring Boot web application, where after successful login, I have to send the user to the OTP page.
My problem is: When the user comes to the OTP page he can bypass it changing the URL, so he can access any page (i.e. big security risk) because the user has already authenticated from the login page.
How can I restict URL changes on the OTP page as it happens on the login page (using Spring boot-security), so the user can only get in if she/he is authenticated by OTP.
A common approach is that on successful authentication - i.e. credentials entered on login screen are verified - the user is given limited access to the application. This limited access only allows access to the OTP page(s). Once the OTP has been verified, the user is given the full set of authorisation roles to which they're entitled.
A blog outlining this approach is available here.
Create an AuthenticationSuccessHandler
If the user requires a one-time password, strip their authorities, and give them a new one, say ROLE_OTP. ROLE_OTP can only use the OTP URL and not anything else.
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication sourceAuthentication
) throws IOException, ServletException {
UserDetails sourceUser = (UserDetails) sourceAuthentication.getPrincipal();
List<GrantedAuthority> targetAuthorities = new ArrayList<GrantedAuthority>( Arrays.asList( new SimpleGrantedAuthority("ROLE_OTP") ) );
UserDetails targetUser = new User( sourceUser.getUsername() , "", targetAuthorities);
UsernamePasswordAuthenticationToken targetAuthentication = new UsernamePasswordAuthenticationToken(targetUser, null, targetAuthorities);
targetAuthentication.setDetails( sourceAuthentication.getDetails() );
SecurityContextHolder.getContext().setAuthentication(targetAuthentication);
response.sendRedirect("/otp-url");
}
If they pass the OTP, reload their real roles with loadUserByUsername()
Authentication sourceAuthentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails sourceUser = (UserDetails) sourceAuthentication.getPrincipal();
UserDetails targetUser = userDetailsManager.loadUserByUsername(sourceUser.getUsername());
UsernamePasswordAuthenticationToken targetAuthentication = new UsernamePasswordAuthenticationToken(targetUser, null, targetUser.getAuthorities());
targetAuthentication.setDetails( sourceAuthentication.getDetails() );
SecurityContextHolder.getContext().setAuthentication(targetAuthentication);

Resources