Create customers, manage authentication and authorization.

Customer account management

In order for a customer to place an order, he/she must create an account first. this allow customers to track their orders, and let the store admin had enough information to deliver the order.

Tradenity API offers the Customer resource which provides all the necessary infrastructure to create and manage user account, login and logout, safely store sensitive information such as password in encrypted format.

In this section we will learn how to integrate Tradenity Customer resource and related services within your application to allow your customers to create and manage their accounts.

Spring Security plugin is the preferred security framework for grails. In order to manage user authentication and authorization in spring security, You must provide at least 3 classes:

  1. UserDetails class which represents a user.
  2. UserDetailsService implementation, which knows how to get that user based on his credentials (username and password)
  3. Configure security path mapping

Implementing these classes is very simple, as Tradenity already implemented the required infrastructure for you, so the spring specific implementation is just a thin layer around what is already there.

Implement UserDetails

Our implementation of UserDetails interface is a wrapper around the Customer class, delegating all the method calls to their corresponding methods in the customer instance


public class User implements Serializable, UserDetails{


    String customerId;
    String firstName;
    String lastName;
    String email;
    String username;
    String password;
    String confirmPassword;
    boolean enabled = true;

    public User() {
    }

    public User(Customer customer) {
        this.customerId = customer.getId();
        this.firstName = customer.getFirstName();
        this.lastName = customer.getLastName();
        this.email = customer.getEmail();
        this.username = customer.getUsername();
        this.password = customer.getPassword();
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCustomerId() {
        return customerId;
    }

    public void setCustomerId(String customerId) {
        this.customerId = customerId;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return AuthorityUtils.createAuthorityList("ROLE_USER");
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public Customer toCustomer() {
        Customer c = new Customer(firstName, lastName, email, username, password, "active");
        c.setId(this.customerId);
        return c;
    }
}


Implement UserDetailsService

Next, Implement the UserDetailsService interface. just a simple wrapper around Tradenity SDK ‘s CustomerService#findByUsername


@Service
public class UserServiceImpl implements UserDetailsService{
    @Autowired
    CustomerService customerService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Customer customer = customerService.findByUsername(username);
        if(customer == null) {
            throw new UsernameNotFoundException("Could not find user " + username);
        }
        return new User(customer);
    }
}

Configure spring security

grails-app/conf/spring/resources.groovy


import tradenity.grails.sample.CustomerDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder

beans = {
    userDetailsService(CustomerDetailsService)
    passwordEncoder(BCryptPasswordEncoder)
}

  • We define a bean of type CustomerDetailsService which implement UserDetailsService. Spring security will pick it automatically and use it to load UserDetails instances upon login attempt.

  • But, What is PasswordEncoder and BCryptPasswordEncoder? Tradenity never store customer passwords as plain text, this is a best practice for security.

Tradenity stores the encrypted password, Using the bcrypt algorithm which makes it nearly impossible for hackers to decrypt it. So, we configure spring security to use the appropriate password encoder.

Now let’s configure spring security to protect our sensitive web paths.

grails-app/conf/application.groovy


grails.plugin.springsecurity.auth.loginFormUrl = '/login'
grails.plugin.springsecurity.successHandler.defaultTargetUrl = '/'
grails.plugin.springsecurity.failureHandler.defaultFailureUrl = '/login?error=1'
grails.plugin.springsecurity.securityConfigType = "InterceptUrlMap"
grails.plugin.springsecurity.interceptUrlMap  = [
        [pattern: '/',               access: ['permitAll']],
        [pattern: '/favicon.ico',    access: ['permitAll']],
        [pattern: '/fonts/**',       access: ['permitAll']],
        [pattern: '/error',          access: ['permitAll']],
        [pattern: '/index',          access: ['permitAll']],
        [pattern: '/products',       access: ['permitAll']],
        [pattern: '/products/**',    access: ['permitAll']],
        [pattern: '/categories/**',  access: ['permitAll']],
        [pattern: '/brands/**',      access: ['permitAll']],
        [pattern: '/cart/**',        access: ['permitAll']],
        [pattern: '/login',          access: ['permitAll']],
        [pattern: '/login/**',       access: ['permitAll']],
        [pattern: '/logout',         access: ['permitAll']],
        [pattern: '/logout/**',      access: ['permitAll']],
        [pattern: '/register',       access: ['permitAll']],
        [pattern: '/orders/**',      access: 'isAuthenticated()'],
        [pattern: '/account/create', access: ['permitAll']],
        [pattern: '/assets/**',      access: ['permitAll']],
        [pattern: '/shutdown',       access: ['permitAll']]
]

This configuration instruct spring security to protect /orders/** path, and permit access to all other actions.

The login form

Now, we have everything in place, and we are ready to authenticate our customers using their username and password. Here is the HTML form for user login.

grails-app/views/account/login.gsp


<form action="/login/authenticate" method="post">
    <ul>
        <li class="text-info">Username: </li>
        <li><input type="text" name="username" value=""/></li>
    </ul>
    <ul>
        <li class="text-info">Password: </li>
        <li><input type="password" name="password" value=""/></li>
    </ul>

    <input type="submit" value="LOGIN"/>

</form>

Register New customers

grails-app/views/account/register.gsp


<form action="/account/create" method="post" >
    <ul>
        <li class="text-info">First Name: </li>
        <li><g:textField name="firstName" value="${user.firstName}"/></li>
    </ul>
    <ul>
        <li class="text-info">Last Name: </li>
        <li><g:textField name="lastName" value="${user.lastName}"/></li>
    </ul>
    <ul>
        <li class="text-info">Email: </li>
        <li><g:textField name="email" value="${user.email}"/></li>
    </ul>
    <ul>
        <li class="text-info">Username: </li>
        <li><g:textField name="username" value="${user.username}"/></li>
    </ul>
    <ul>
        <li class="text-info">Password: </li>
        <li><g:passwordField name="password" value="${user.password}"/></li>
    </ul>
    <ul>
        <li class="text-info">Re-enter Password:</li>
        <li><g:passwordField name="confirmPassword" value="${user.confirmPassword}"/></li>
    </ul>

    <input type="submit" value="REGISTER NOW"/>
    <p class="click">By clicking this button, you are agree to my  <a href="#">Policy Terms and Conditions.</a></p>
</form>

and this is the action handler mwthod, it validate the imput and call CustomerService#create method.

grails-app/controllers/AccountController.groovy


def create() {
    def u = new User(firstName: params.firstName, lastName: params.lastName, email: params.email,
            username: params.username, password: params.password, confirmPassword: params.confirmPassword
    )
    if(u.valid()){
        def c = customerService.create(u.toCustomer())
        redirect action: "login"
    }else {
        println "invalid"
        render view: "register", model: [user: u,
                                         categories: categoryService.findAll(),
                                         cart: shoppingCartService.get()]
    }
}