diff --git a/src/main/java/no/nibio/vips/logic/controller/session/UserBean.java b/src/main/java/no/nibio/vips/logic/controller/session/UserBean.java
index 71c58c1aa7f6ada6dd27a6831e02380f41cd9398..3392a85aeda56ffa4affd1c777d0a874412d8709 100755
--- a/src/main/java/no/nibio/vips/logic/controller/session/UserBean.java
+++ b/src/main/java/no/nibio/vips/logic/controller/session/UserBean.java
@@ -55,20 +55,7 @@ import javax.validation.ConstraintViolationException;
 import javax.ws.rs.core.HttpHeaders;
 
 import no.nibio.vips.logic.authenticate.PasswordValidationException;
-import no.nibio.vips.logic.entity.Country;
-import no.nibio.vips.logic.entity.ForecastConfiguration;
-import no.nibio.vips.logic.entity.MapLayer;
-import no.nibio.vips.logic.entity.MessageLocale;
-import no.nibio.vips.logic.entity.Observation;
-import no.nibio.vips.logic.entity.Organization;
-import no.nibio.vips.logic.entity.OrganizationGroup;
-import no.nibio.vips.logic.entity.PointOfInterest;
-import no.nibio.vips.logic.entity.UserAuthentication;
-import no.nibio.vips.logic.entity.UserAuthenticationPK;
-import no.nibio.vips.logic.entity.UserAuthenticationType;
-import no.nibio.vips.logic.entity.UserUuid;
-import no.nibio.vips.logic.entity.UserUuidPK;
-import no.nibio.vips.logic.entity.VipsLogicUser;
+import no.nibio.vips.logic.entity.*;
 import no.nibio.vips.logic.entity.misc.UserResources;
 import no.nibio.vips.logic.i18n.SessionLocaleUtil;
 import no.nibio.vips.logic.messaging.MessagingBean;
@@ -143,6 +130,10 @@ public class UserBean {
             return null;
         }
     }
+
+    public VipsLogicRole getVipsLogicRole(Integer vipsLogicRoleId){
+        return em.find(VipsLogicRole.class, vipsLogicRoleId);
+    }
     
     /**
      * Gets a user with get given userName and type of authentication
diff --git a/src/main/java/no/nibio/vips/logic/service/LogicService.java b/src/main/java/no/nibio/vips/logic/service/LogicService.java
index 8e424b9065f83dc0a7ee64564f8c7969658ca454..7335b48ee2af97db528261d645e871abd2e7230d 100755
--- a/src/main/java/no/nibio/vips/logic/service/LogicService.java
+++ b/src/main/java/no/nibio/vips/logic/service/LogicService.java
@@ -19,30 +19,27 @@
 
 package no.nibio.vips.logic.service;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.ibm.icu.util.ULocale;
 import com.webcohesion.enunciate.metadata.Facet;
 import com.webcohesion.enunciate.metadata.rs.TypeHint;
-import java.util.TimeZone;
+
+import java.io.IOException;
+import java.util.*;
+
 import de.micromata.opengis.kml.v_2_2_0.Kml;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import javax.ejb.EJB;
+import javax.persistence.NonUniqueResultException;
 import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import javax.ws.rs.*;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.client.ClientBuilder;
 import javax.ws.rs.client.WebTarget;
@@ -53,37 +50,25 @@ import javax.ws.rs.core.Response.Status;
 import no.nibio.vips.coremanager.service.ManagerResource;
 import no.nibio.vips.entity.WeatherObservation;
 import no.nibio.vips.logic.authenticate.PasswordValidationException;
-import no.nibio.vips.logic.controller.session.ForecastBean;
-import no.nibio.vips.logic.controller.session.MessageBean;
-import no.nibio.vips.logic.controller.session.OrganismBean;
-import no.nibio.vips.logic.controller.session.PointOfInterestBean;
-import no.nibio.vips.logic.controller.session.UserBean;
-import no.nibio.vips.logic.entity.CropCategory;
-import no.nibio.vips.logic.entity.CropPest;
-import no.nibio.vips.logic.entity.ForecastResult;
+import no.nibio.vips.logic.controller.servlet.UserController;
+import no.nibio.vips.logic.controller.session.*;
+import no.nibio.vips.logic.entity.*;
 import no.nibio.vips.logic.i18n.SessionLocaleUtil;
-import no.nibio.vips.logic.entity.ForecastConfiguration;
-import no.nibio.vips.logic.entity.ForecastModelConfiguration;
-import no.nibio.vips.logic.entity.Message;
-import no.nibio.vips.logic.entity.MessageTag;
-import no.nibio.vips.logic.entity.ModelInformation;
-import no.nibio.vips.logic.entity.Organism;
-import no.nibio.vips.logic.entity.Organization;
-import no.nibio.vips.logic.entity.PointOfInterest;
-import no.nibio.vips.logic.entity.PointOfInterestType;
-import no.nibio.vips.logic.entity.PointOfInterestWeatherStation;
-import no.nibio.vips.logic.entity.VipsLogicUser;
+import no.nibio.vips.logic.util.Globals;
 import no.nibio.vips.logic.util.SystemTime;
 import no.nibio.vips.observationdata.ObservationDataBean;
 import no.nibio.vips.util.CSVPrintUtil;
 import no.nibio.vips.util.ServletUtil;
 import no.nibio.vips.util.SolarRadiationUtil;
+import no.nibio.web.forms.FormValidationException;
 import org.jboss.resteasy.annotations.GZIP;
 import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
 import org.jboss.resteasy.spi.HttpRequest;
 
+import org.apache.commons.validator.routines.EmailValidator;
+
 /**
- * @copyright 2013-2022 <a href="http://www.nibio.no/">NIBIO</a>
+ * @copyright 2013-2023 <a href="http://www.nibio.no/">NIBIO</a>
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 @Path("rest")
@@ -1337,6 +1322,139 @@ public class LogicService {
         return retVal != null ? Response.ok().entity(retVal).build() 
                 : Response.status(Response.Status.NOT_FOUND).entity("ERROR: Could not find model with id=" + modelId).build();
     }
+
+
+    /**
+     * Registers a user and grants limited access to certain functionalities in the VIPSLogic system:
+     * <ul>
+     *     <li>Adding observations - default not approved</li>
+     *     <li>Adding POIs (Points Of Interest)</li>
+     * </ul>
+     * The user must be approved
+     * @param userInfoBody
+     * @return
+     */
+    @POST
+    @Path("user/register")
+    @Consumes("application/json;charset=UTF-8")
+    @Produces("application/json;charset=UTF-8")
+    public Response registerNewLimitedUser(String userInfoBody)
+    {
+
+        try {
+            HashMap<String, Object> userInfo = new ObjectMapper().readValue(userInfoBody, new TypeReference<HashMap<String, Object>>() {
+            });
+            // Input control
+            List<String> errorMessages = new ArrayList<>();
+            // Email
+
+            String email = ((String) userInfo.get("email")).toLowerCase();
+            // Set?
+            if(email == null || email.isBlank())
+            {
+                errorMessages.add("Email must be set");
+            }
+            // Must be valid email
+            else if(!EmailValidator.getInstance().isValid(email))
+            {
+                errorMessages.add(email + " is not a valid email address");
+            }
+            else
+            {
+                // Must be unique
+                Boolean emailAlreadyInUse = false;
+                try {
+                    VipsLogicUser foundUser = userBean.getUserByEmail(email);
+                    emailAlreadyInUse = (foundUser != null);
+                } catch (NonUniqueResultException ex) {
+                    emailAlreadyInUse = true;
+                }
+                if (emailAlreadyInUse) {
+                    errorMessages.add("Email " + email + " is already in use");
+                }
+            }
+
+            // Username
+            String username = (String) userInfo.get("username");
+            // Set?
+            if(username == null || username.isBlank())
+            {
+                errorMessages.add("Username must be set");
+            }
+            else
+            {
+                // Existing username?
+                Boolean usernameExists = false;
+                try
+                {
+                    VipsLogicUser foundUser = userBean.getUser(username, UserAuthenticationType.TYPE_PASSWORD);
+                    usernameExists = (foundUser != null);
+                }
+                catch(NonUniqueResultException ex)
+                {
+                    usernameExists = true;
+                }
+                if(usernameExists)
+                {
+                    errorMessages.add("Username " + username  + " already exists");
+                }
+            }
+
+            // First name
+            String firstName = (String) userInfo.get("firstName");
+            if(firstName == null || firstName.isBlank())
+            {
+                errorMessages.add("First name must be set");
+            }
+
+            // Last name
+            String lastName = (String) userInfo.get("lastName");
+            if(lastName == null || lastName.isBlank())
+            {
+                errorMessages.add("Last name must be set");
+            }
+
+
+            // Password
+            String password = (String) userInfo.get("password");
+            if(password == null || password.isBlank())
+            {
+                errorMessages.add("Password must be set");
+            }
+
+            if(errorMessages.size() > 0)
+            {
+                Map<String, List<String>> errorMsg = Map.of("errorMessages",errorMessages);
+                return Response.status(Status.BAD_REQUEST).entity(errorMsg).build();
+            }
+
+            VipsLogicUser user = new VipsLogicUser();
+            user.setFirstName(firstName.trim());
+            user.setLastName(lastName.trim());
+            user.setEmail(email.trim());
+            user.setPhoneCountryCode((String) userInfo.get("phoneCountryCode"));
+            user.setPreferredLocale((String) userInfo.get("preferredLocale"));
+            user.setOrganizationId(userBean.getOrganization((Integer) userInfo.get("organizationId")));
+            user.setApprovalApplication("Registered in app");
+            user.setUserStatusId(Globals.USER_STATUS_AWAITING_EMAIL_VERIFICATION);
+            // Add observer role
+            user.setVipsLogicRoles(Set.of(userBean.getVipsLogicRole(VipsLogicRole.OBSERVER)));
+            // Set user authentication
+            UserAuthenticationType uat = userBean.createUserAuthenticationTypeInstance(UserAuthenticationType.TYPE_PASSWORD);
+            UserAuthentication ua = new UserAuthentication();
+            ua.setUserAuthenticationType(uat);
+            ua.setUsername(username.trim());
+            ua.setPassword(userBean.getMD5EncryptedString(password.trim()));
+            userBean.storeUserFirstTime(user, ua);
+            userBean.sendUserEmailVerification(user, SessionLocaleUtil.getI18nBundle(httpServletRequest), ServletUtil.getServerName(httpServletRequest));
+
+            return Response.status(Status.OK).entity(user).build();
+        }
+        catch(FormValidationException | IOException ex)
+        {
+            return Response.status(Status.BAD_REQUEST).entity("INPUT ERROR: " + ex.getMessage()).build();
+        }
+    }
     
     /**
      * Get the client to use for calling VIPSCoreManager REST services programmatically