diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4d81aedbff0bfe574964db1a86e6bd0ca6ed290..9d2d7883229dd069113850e5512a34b7d8b737ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
 # 2.0.14
+* [LGR-75](https://jira.software.geant.org/browse/LGR-73) - Externalise saml groups to role
 * [LGR-73](https://jira.software.geant.org/browse/LGR-73) - Remove router endpoint
 * [LGR-72](https://jira.software.geant.org/browse/LGR-72) - Check user is authenticated prior to executing commands on internal routers
 
diff --git a/src/main/java/org/geant/lgservice/config/CommandsParser.java b/src/main/java/org/geant/lgservice/config/CommandsParser.java
index f3051fa7b626ae16504d368d797dde0c84d80012..1f8ee9526713d86b39af8ccafa8808d2717164b8 100644
--- a/src/main/java/org/geant/lgservice/config/CommandsParser.java
+++ b/src/main/java/org/geant/lgservice/config/CommandsParser.java
@@ -1,77 +1,61 @@
 package org.geant.lgservice.config;
 
-import com.google.common.collect.ImmutableMap;
-import lombok.Builder;
 import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
+import org.geant.lgservice.exceptions.TechnicalException;
 import org.geant.lgservice.pojos.Group;
 import org.geant.lgservice.security.LGUser;
 import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Unmarshaller;
 import java.io.InputStream;
-import java.util.Collection;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Predicate;
+import java.util.Optional;
 
 public class CommandsParser {
 
-	private static final Logger logger = LogManager.getLogger(CommandsParser.class);
-
-	public List<Group> getCommandsFromXML(final String fileName, final LGUser user) {
-		Commands commands = new Commands();
-		try {
-			String newFileName = resolveFileName(fileName, user);
-			if (newFileName != null) {
-				ClassLoader classLoader = getClass().getClassLoader();
-				InputStream stream = classLoader.getResourceAsStream(newFileName);
-
-				JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class);
-				Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
-				commands = (Commands) unmarshaller.unmarshal(stream);
-			} else {
-				logger.error("Unable to read config file for commands list");
-			}
-		} catch (JAXBException e) {
-			logger.error("Unable to parse xml for " + fileName, e);
-		}
-
-		return commands.getGroups();
-	}
-
-	@Builder
-	@Getter
-	private static class FileName {
-		private final String accessLevel;
-		private final int rank;
-	}
-
-	private static final Map<Predicate<Collection<GrantedAuthority>>, FileName> FILE_NAME_PREDICATES = ImmutableMap
-			.<Predicate<Collection<GrantedAuthority>>, FileName>builder()
-			.put(
-					grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.INTERNAL),
-					FileName.builder().accessLevel("internal").rank(0).build())
-			.put(
-					grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.EXTERNAL),
-					FileName.builder().accessLevel("external").rank(1).build())
-			.build();
-
-	public String resolveFileName(final String fileName, final LGUser user) {
-		String accessLevel = "public";
-		if (user != null) {
-			accessLevel = FILE_NAME_PREDICATES.entrySet()
-					.stream()
-					.filter(entry -> entry.getKey().test(user.getAuthorities()))
-					.map(Map.Entry::getValue)
-					.reduce((fileName1, fileName2) -> fileName1.getRank() < fileName2.getRank() ? fileName1 : fileName2)
-					.map(FileName::getAccessLevel)
-					.orElse("public");
-		}
-
-		return String.format("%s_%s.xml", fileName, accessLevel);
-	}
+    private static final Logger logger = LogManager.getLogger(CommandsParser.class);
+
+    public List<Group> getCommandsFromXML(final String fileName, final LGUser user) {
+        try {
+            InputStream stream = getClass().getClassLoader().getResourceAsStream(resolveFileName(fileName, user));
+
+            JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class);
+            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+            return ((Commands) unmarshaller.unmarshal(stream)).getGroups();
+        } catch (JAXBException e) {
+            logger.error("Unable to parse xml for " + fileName, e);
+            throw new TechnicalException(e);
+        }
+
+    }
+
+    @RequiredArgsConstructor
+    private enum GroupRanking {
+        INTERNAL(0),
+        EXTERNAL(1);
+
+        @Getter
+        private final int rank;
+    }
+
+    public String resolveFileName(final String fileName, final LGUser user) {
+        String accessLevel = Optional.ofNullable(user)
+                .map(UserDetails::getAuthorities)
+                .flatMap(authorities -> authorities
+                        .stream()
+                        .map(GrantedAuthority::getAuthority)
+                        .map(GroupRanking::valueOf)
+                        .reduce((groupOne, groupTwo) -> groupOne.getRank() < groupTwo.getRank() ? groupOne : groupTwo))
+                .map(GroupRanking::name)
+                .map(String::toLowerCase)
+                .orElse("public");
+
+        return String.format("%s_%s.xml", fileName, accessLevel);
+    }
 }
diff --git a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
index 02003fac615c7070ee22e8bb99cf67f1915a5623..17ac48d40a7878533f62cffb126e8d02c7bebc28 100644
--- a/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
+++ b/src/main/java/org/geant/lgservice/infrastructure/rest/inventoryprovider/InventoryProviderRouterRepository.java
@@ -9,7 +9,6 @@ import lombok.Getter;
 import lombok.extern.slf4j.Slf4j;
 import okhttp3.Interceptor;
 import okhttp3.OkHttpClient;
-import okhttp3.Request;
 import org.geant.lgservice.domain.Router;
 import org.geant.lgservice.domain.RouterRepository;
 import org.geant.lgservice.exceptions.TechnicalException;
diff --git a/src/main/java/org/geant/lgservice/security/LGUser.java b/src/main/java/org/geant/lgservice/security/LGUser.java
index 340631cda86d2750ae7dead3b22ef295621bcee7..4aa362ed5a388b9a2060e2203bb54ad35c542b31 100644
--- a/src/main/java/org/geant/lgservice/security/LGUser.java
+++ b/src/main/java/org/geant/lgservice/security/LGUser.java
@@ -3,95 +3,67 @@ package org.geant.lgservice.security;
 import lombok.Builder;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
+import lombok.Singular;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import java.util.Collection;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import static java.util.stream.Collectors.toSet;
 
+@Builder
 @Getter
 public class LGUser implements UserDetails {
 
-	private final String federatedUser;
-	private final String givenName;
-	private final String surname;
-	private final String mail;
-	private final Collection<GrantedAuthority> authorities;
-
-	@RequiredArgsConstructor
-	public enum Group implements GrantedAuthority{
-		INTERNAL(new SimpleGrantedAuthority("GEANT Staff:All")),
-		EXTERNAL(new SimpleGrantedAuthority("GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"));
-
-		private final GrantedAuthority authority;
-
-		public static Optional<Group> fromGroup(String group) {
-			return Stream.of(Group.values())
-					.filter(value -> value.authority.getAuthority().equals(group))
-					.findFirst();
-		}
-
-		public static Optional<Group> fromAuthority(GrantedAuthority authority) {
-			return Stream.of(Group.values())
-					.filter(authority::equals)
-					.findFirst();
-		}
-
-		@Override
-		public String getAuthority() {
-			return name();
-		}
-	}
-
-	@Builder
-	public LGUser(String federatedUser, String givenName, String surname, String mail,
-			String ...groups) {
-
-		this.federatedUser = federatedUser;
-		this.givenName = givenName;
-		this.surname = surname;
-		this.mail = mail;
-		this.authorities = Stream.of(groups)
-				.map(Group::fromGroup)
-				.filter(Optional::isPresent)
-				.map(Optional::get)
-				.collect(toSet());
-
-
-	}
-
-	@Override
-	public String getPassword() {
-		return null;
-	}
-
-	@Override
-	public String getUsername() {
-		return federatedUser;
-	}
-
-	@Override
-	public boolean isAccountNonExpired() {
-		return true;
-	}
-
-	@Override
-	public boolean isAccountNonLocked() {
-		return true;
-	}
-
-	@Override
-	public boolean isCredentialsNonExpired() {
-		return true;
-	}
-
-	@Override
-	public boolean isEnabled() {
-		return true;
-	}
+    private final String federatedUser;
+    private final String givenName;
+    private final String surname;
+    private final String mail;
+    @Singular
+    private final Collection<Group> groups;
+
+    @RequiredArgsConstructor
+    public enum Group implements GrantedAuthority{
+        INTERNAL,
+        EXTERNAL;
+
+        @Override
+        public String getAuthority() {
+            return name();
+        }
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return getGroups();
+    }
+
+    @Override
+    public String getPassword() {
+        return null;
+    }
+
+    @Override
+    public String getUsername() {
+        return federatedUser;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
 
 }
diff --git a/src/main/java/org/geant/lgservice/security/LGUserService.java b/src/main/java/org/geant/lgservice/security/LGUserService.java
index a0ac88e9bc512327909cdaf52c8f52d6fad5358e..6949949ab3c245652e87b76e2136cff59bc10c38 100644
--- a/src/main/java/org/geant/lgservice/security/LGUserService.java
+++ b/src/main/java/org/geant/lgservice/security/LGUserService.java
@@ -1,5 +1,7 @@
 package org.geant.lgservice.security;
 
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -7,26 +9,47 @@ import org.springframework.security.saml.SAMLCredential;
 import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
+
 @Service
+@RequiredArgsConstructor
+@Slf4j
 public class LGUserService implements SAMLUserDetailsService {
 
-	private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
-
-	@Override
-	public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
-		String federatedUser = credential.getAttributeAsString("uid");
-		String givenName = credential.getAttributeAsString("givenName");
-		String surname = credential.getAttributeAsString("sn");
-		String mail = credential.getAttributeAsString("mail");
-		String[] groupNames = credential.getAttributeAsStringArray("isMemberOf");
-
-		LOGGER.info("Current user : " + givenName + ", " + surname);
-		return LGUser.builder()
-				.federatedUser(federatedUser)
-				.givenName(givenName)
-				.surname(surname)
-				.mail(mail)
-				.groups(groupNames)
-				.build();
-	}
+    private final Map<SamlKeyMappings.Key, String> keyMappings;
+    private final Map<String, LGUser.Group> groupMappings;
+
+    @Override
+    public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
+        log.debug("Extracting attributes using keys: {}", keyMappings);
+
+        String federatedUser = credential.getAttributeAsString(keyMappings.get(ID));
+        String givenName = credential.getAttributeAsString(keyMappings.get(GIVEN_NAME));
+        String surname = credential.getAttributeAsString(keyMappings.get(SURNAME));
+        String mail = credential.getAttributeAsString(keyMappings.get(EMAIL));
+
+        Collection<LGUser.Group> groups = Arrays.stream(credential.getAttributeAsStringArray(keyMappings.get(GROUPS)))
+                .map(groupMappings::get)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        log.info("Current user : " + givenName + ", " + surname);
+        return LGUser.builder()
+                .federatedUser(federatedUser)
+                .givenName(givenName)
+                .surname(surname)
+                .mail(mail)
+                .groups(groups)
+                .build();
+    }
 }
diff --git a/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java b/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java
new file mode 100644
index 0000000000000000000000000000000000000000..4bdbd6ded72d096807f2f740d6ec74fe51e5e960
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/security/SamlGroupMapping.java
@@ -0,0 +1,24 @@
+package org.geant.lgservice.security;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConstructorBinding;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.unmodifiableMap;
+
+@ConstructorBinding
+@ConfigurationProperties("saml")
+public class SamlGroupMapping {
+    private final Map<LGUser.Group, String> groupMappings;
+
+    public SamlGroupMapping(Map<LGUser.Group, String> groupMappings) {
+        this.groupMappings = groupMappings;
+    }
+
+    public Map<String, LGUser.Group> groupMappings() {
+        return unmodifiableMap(groupMappings.entrySet().stream()
+                .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java b/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java
new file mode 100644
index 0000000000000000000000000000000000000000..7c2551775e4800c7cd89ad6d90ff4bd1f953b64b
--- /dev/null
+++ b/src/main/java/org/geant/lgservice/security/SamlKeyMappings.java
@@ -0,0 +1,38 @@
+package org.geant.lgservice.security;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConstructorBinding;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.String.format;
+import static java.util.Collections.unmodifiableMap;
+
+@ConstructorBinding
+@ConfigurationProperties("saml")
+public class SamlKeyMappings {
+
+    public enum Key {
+        ID,
+        GIVEN_NAME,
+        SURNAME,
+        EMAIL,
+        GROUPS
+    }
+
+    private final Map<Key, String> keyMappings;
+
+    public SamlKeyMappings(Map<Key, String> keyMappings) {
+        checkNotNull(keyMappings, "Missing saml key mappings, under 'saml.key-mappings'");
+        checkArgument(keyMappings.size() == Key
+                .values().length, format("Missing saml key mappings, required: %s", Arrays.toString(Key.values())));
+        this.keyMappings = keyMappings;
+    }
+
+    public Map<Key, String> mappings() {
+        return unmodifiableMap(keyMappings);
+    }
+}
diff --git a/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java b/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
index 2f27ad859d0c82e997cdf4d6212a5f343706c5f9..349490dd5ea24c1088569d13f1d4bb2aacb4f953 100644
--- a/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
+++ b/src/main/java/org/geant/lgservice/security/WebSecurityConfig.java
@@ -11,6 +11,7 @@ import org.opensaml.xml.parse.StaticBasicParserPool;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.io.DefaultResourceLoader;
@@ -71,65 +72,70 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import java.util.*;
+import java.util.Timer;
 
 import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
 
 @Configuration
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(securedEnabled = true)
+@EnableConfigurationProperties({
+        SamlGroupMapping.class,
+        SamlKeyMappings.class
+})
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
-	
-	@Autowired
-	AppConfig config;
-	
-	private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
-	
-	private String metadataURL;
- 
-	private Timer backgroundTaskTimer;
-	private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
-	
-	@PostConstruct
-	public void init() {
-		this.backgroundTaskTimer = new Timer(true);
-		this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
-		this.metadataURL = config.getMetadataURL();
-	}
-	
-	@PreDestroy
-	public void destroy() {
-		this.backgroundTaskTimer.purge();
-		this.backgroundTaskTimer.cancel();
-		this.multiThreadedHttpConnectionManager.shutdown();
-	}
-	
+
+    @Autowired
+    AppConfig config;
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
+
+    private String metadataURL;
+
+    private Timer backgroundTaskTimer;
+    private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
+
+    @PostConstruct
+    public void init() {
+        this.backgroundTaskTimer = new Timer(true);
+        this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
+        this.metadataURL = config.getMetadataURL();
+    }
+
+    @PreDestroy
+    public void destroy() {
+        this.backgroundTaskTimer.purge();
+        this.backgroundTaskTimer.cancel();
+        this.multiThreadedHttpConnectionManager.shutdown();
+    }
+
     @Autowired
     private LGUserService userService;
-     
+
     @Bean
     public VelocityEngine velocityEngine() {
         return VelocityFactory.getEngine();
     }
- 
+
     @Bean(initMethod = "initialize")
     public StaticBasicParserPool parserPool() {
         return new StaticBasicParserPool();
     }
- 
+
     @Bean(name = "parserPoolHolder")
     public ParserPoolHolder parserPoolHolder() {
         return new ParserPoolHolder();
     }
- 
+
     // Bindings, encoders and decoders used for creating and parsing messages
     @Bean
-	public HttpClient httpClient() {
-		HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
-		httpClient.getHostConfiguration().setHost(
-		        fromHttpUrl(metadataURL).build().toUri().getHost());
-		return httpClient;
-	}
- 
+    public HttpClient httpClient() {
+        HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
+        httpClient.getHostConfiguration().setHost(
+                fromHttpUrl(metadataURL).build().toUri().getHost());
+        return httpClient;
+    }
+
     // SAML Authentication Provider responsible for validating of received SAML
     // messages
     @Bean
@@ -139,77 +145,77 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlAuthenticationProvider.setForcePrincipalAsString(false);
         return samlAuthenticationProvider;
     }
- 
+
     // Provider of default SAML Context
     @Bean
     public SAMLContextProviderImpl contextProvider() {
         return new SAMLContextProviderImpl();
     }
- 
+
     // Initialization of OpenSAML library
     @Bean
     public static SAMLBootstrap sAMLBootstrap() {
         return new SAMLBootstrap();
     }
- 
+
     // Logger for SAML messages and events
     @Bean
     public SAMLDefaultLogger samlLogger() {
         return new SAMLDefaultLogger();
     }
- 
+
     // SAML 2.0 WebSSO Assertion Consumer
     @Bean
     public WebSSOProfileConsumer webSSOprofileConsumer() {
         return new WebSSOProfileConsumerImpl();
     }
- 
+
     // SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
     @Bean
     public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
         return new WebSSOProfileConsumerHoKImpl();
     }
- 
+
     // SAML 2.0 Web SSO profile
     @Bean
     public WebSSOProfile webSSOprofile() {
         return new WebSSOProfileImpl();
     }
- 
+
     // SAML 2.0 Holder-of-Key Web SSO profile
     @Bean
     public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
         return new WebSSOProfileConsumerHoKImpl();
     }
- 
+
     // SAML 2.0 ECP profile
     @Bean
     public WebSSOProfileECPImpl ecpprofile() {
         return new WebSSOProfileECPImpl();
     }
- 
+
     @Bean
     public SingleLogoutProfile logoutprofile() {
         return new SingleLogoutProfileImpl();
     }
-    
+
     @Bean
-	public KeyManager keyManager() {
-    	DefaultResourceLoader loader = new DefaultResourceLoader();
-		Resource storeFile = loader.getResource(config.getKeyStore());
-		String storePass = config.getKeystorePassword();
-		Map<String, String> passwords = new HashMap<String, String>();
-		passwords.put(config.getKeystoreAlias(), config.getKeystorePassword());
-		return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias());
-	}
-    
+    public KeyManager keyManager() {
+        DefaultResourceLoader loader = new DefaultResourceLoader();
+        Resource storeFile = loader.getResource(config.getKeyStore());
+        String storePass = config.getKeystorePassword();
+        Map<String, String> passwords = new HashMap<String, String>();
+        passwords.put(config.getKeystoreAlias(), config.getKeystorePassword());
+        return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias());
+    }
+
     @Bean
     public WebSSOProfileOptions defaultWebSSOProfileOptions() {
         WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
         webSSOProfileOptions.setIncludeScoping(false);
         return webSSOProfileOptions;
     }
- 
+
     // Entry point to initialize authentication, default values taken from
     // properties file
     @Bean
@@ -218,19 +224,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
         return samlEntryPoint;
     }
-    
- // Setup advanced info about metadata
- 	@Bean
- 	public ExtendedMetadata extendedMetadata() {
- 		ExtendedMetadata extendedMetadata = new ExtendedMetadata();
- 		extendedMetadata.setIdpDiscoveryEnabled(false);
- 		extendedMetadata.setSignMetadata(false);
- 		extendedMetadata.setEcpEnabled(false);
- 		extendedMetadata.setSslHostnameVerification("allowAll");
- 		extendedMetadata.setSigningKey("spring");
- 		return extendedMetadata;
- 	}
-    
+
+    // Setup advanced info about metadata
+    @Bean
+    public ExtendedMetadata extendedMetadata() {
+        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
+        extendedMetadata.setIdpDiscoveryEnabled(false);
+        extendedMetadata.setSignMetadata(false);
+        extendedMetadata.setEcpEnabled(false);
+        extendedMetadata.setSslHostnameVerification("allowAll");
+        extendedMetadata.setSigningKey("spring");
+        return extendedMetadata;
+    }
+
     // IDP Discovery Service
     @Bean
     public SAMLDiscovery samlIDPDiscovery() {
@@ -238,46 +244,46 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         idpDiscovery.setIdpSelectionPath("/saml/idpSelection");
         return idpDiscovery;
     }
-    
-	@Bean(name = "metadata")
-	public CachingMetadataManager metadata() throws MetadataProviderException {
-
-		HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(),
-				this.metadataURL);
-
-		//provider.setMinRefreshDelay(3600000);
-		//provider.setMinRefreshDelay(3600000);
-		provider.setParserPool(parserPool());
-
-		ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata());
-		extendedMetadataDelegate.setMetadataTrustCheck(false);
-		extendedMetadataDelegate.setMetadataRequireSignature(false);
-		backgroundTaskTimer.purge();
-
-		List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
-		providers.add(extendedMetadataDelegate);
-		return new CachingMetadataManager(providers);
-
-	}
- 
-	@Bean
-	public MetadataGenerator metadataGenerator() {
-		MetadataGenerator metadataGenerator = new MetadataGenerator();
-		metadataGenerator.setEntityId(config.getEntityName());
-		metadataGenerator.setEntityBaseURL(config.getEntityBaseURL());
-		metadataGenerator.setExtendedMetadata(extendedMetadata());
-		metadataGenerator.setIncludeDiscoveryExtension(false);
-		metadataGenerator.setKeyManager(keyManager());
-		return metadataGenerator;
-	}
- 
+
+    @Bean(name = "metadata")
+    public CachingMetadataManager metadata() throws MetadataProviderException {
+
+        HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(),
+                this.metadataURL);
+
+        //provider.setMinRefreshDelay(3600000);
+        //provider.setMinRefreshDelay(3600000);
+        provider.setParserPool(parserPool());
+
+        ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata());
+        extendedMetadataDelegate.setMetadataTrustCheck(false);
+        extendedMetadataDelegate.setMetadataRequireSignature(false);
+        backgroundTaskTimer.purge();
+
+        List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
+        providers.add(extendedMetadataDelegate);
+        return new CachingMetadataManager(providers);
+
+    }
+
+    @Bean
+    public MetadataGenerator metadataGenerator() {
+        MetadataGenerator metadataGenerator = new MetadataGenerator();
+        metadataGenerator.setEntityId(config.getEntityName());
+        metadataGenerator.setEntityBaseURL(config.getEntityBaseURL());
+        metadataGenerator.setExtendedMetadata(extendedMetadata());
+        metadataGenerator.setIncludeDiscoveryExtension(false);
+        metadataGenerator.setKeyManager(keyManager());
+        return metadataGenerator;
+    }
+
     // The filter is waiting for connections on URL suffixed with filterSuffix
     // and presents SP metadata there
     @Bean
     public MetadataDisplayFilter metadataDisplayFilter() {
         return new MetadataDisplayFilter();
     }
-     
+
     // Handler deciding where to redirect user after successful login
     @Bean
     public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
@@ -287,17 +293,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         LOGGER.info("executing the authentication success handler");
         return successRedirectHandler;
     }
-    
-	// Handler deciding where to redirect user after failed login
+
+    // Handler deciding where to redirect user after failed login
     @Bean
     public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
-    	SimpleUrlAuthenticationFailureHandler failureHandler =
-    			new SimpleUrlAuthenticationFailureHandler();
-    	failureHandler.setUseForward(true);
-    	failureHandler.setDefaultFailureUrl("/error");
-    	return failureHandler;
+        SimpleUrlAuthenticationFailureHandler failureHandler =
+                new SimpleUrlAuthenticationFailureHandler();
+        failureHandler.setUseForward(true);
+        failureHandler.setDefaultFailureUrl("/error");
+        return failureHandler;
     }
-     
+
     @Bean
     public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
         SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
@@ -306,7 +312,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
         return samlWebSSOHoKProcessingFilter;
     }
-    
+
     // Processing filter for WebSSO profile messages
     @Bean
     public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
@@ -316,12 +322,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
         return samlWebSSOProcessingFilter;
     }
-     
+
     @Bean
     public MetadataGeneratorFilter metadataGeneratorFilter() {
         return new MetadataGeneratorFilter(metadataGenerator());
     }
-     
+
     // Handler for successful logout
     @Bean
     public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
@@ -329,17 +335,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         successLogoutHandler.setDefaultTargetUrl("/");
         return successLogoutHandler;
     }
-     
+
     // Logout handler terminating local session
     @Bean
     public SecurityContextLogoutHandler logoutHandler() {
-        SecurityContextLogoutHandler logoutHandler = 
-        		new SecurityContextLogoutHandler();
+        SecurityContextLogoutHandler logoutHandler =
+                new SecurityContextLogoutHandler();
         logoutHandler.setInvalidateHttpSession(true);
         logoutHandler.setClearAuthentication(true);
         return logoutHandler;
     }
- 
+
     // Filter processing incoming logout messages
     // First argument determines URL user will be redirected to after successful
     // global logout
@@ -348,59 +354,59 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
         return new SAMLLogoutProcessingFilter(successLogoutHandler(),
                 logoutHandler());
     }
-     
+
     // Overrides default logout processing filter with the one processing SAML
     // messages
     @Bean
     public SAMLLogoutFilter samlLogoutFilter() {
         return new SAMLLogoutFilter(successLogoutHandler(),
-                new LogoutHandler[] { logoutHandler() },
-                new LogoutHandler[] { logoutHandler() });
+                new LogoutHandler[]{logoutHandler()},
+                new LogoutHandler[]{logoutHandler()});
     }
-	
+
     @Bean
     public HTTPSOAP11Binding soapBinding() {
         return new HTTPSOAP11Binding(parserPool());
     }
-    
+
     @Bean
     public HTTPPostBinding httpPostBinding() {
-    	return new HTTPPostBinding(parserPool(), velocityEngine());
+        return new HTTPPostBinding(parserPool(), velocityEngine());
     }
-    
+
     @Bean
     public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
-    	return new HTTPRedirectDeflateBinding(parserPool());
+        return new HTTPRedirectDeflateBinding(parserPool());
     }
-    
+
     @Bean
     public HTTPSOAP11Binding httpSOAP11Binding() {
-    	return new HTTPSOAP11Binding(parserPool());
+        return new HTTPSOAP11Binding(parserPool());
     }
-    
+
     @Bean
     public HTTPPAOS11Binding httpPAOS11Binding() {
-    	return new HTTPPAOS11Binding(parserPool());
+        return new HTTPPAOS11Binding(parserPool());
     }
-    
+
     // Processor
-	@Bean
-	public SAMLProcessorImpl processor() {
-		Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
-		bindings.add(httpRedirectDeflateBinding());
-		bindings.add(httpPostBinding());
-		//bindings.add(artifactBinding(parserPool(), velocityEngine()));
-		bindings.add(httpSOAP11Binding());
-		bindings.add(httpPAOS11Binding());
-		return new SAMLProcessorImpl(bindings);
-	}
-    
-	/**
-	 * Define the security filter chain in order to support SSO Auth by using SAML 2.0
-	 * 
-	 * @return Filter chain proxy
-	 * @throws Exception
-	 */
+    @Bean
+    public SAMLProcessorImpl processor() {
+        Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
+        bindings.add(httpRedirectDeflateBinding());
+        bindings.add(httpPostBinding());
+        //bindings.add(artifactBinding(parserPool(), velocityEngine()));
+        bindings.add(httpSOAP11Binding());
+        bindings.add(httpPAOS11Binding());
+        return new SAMLProcessorImpl(bindings);
+    }
+
+    /**
+     * Define the security filter chain in order to support SSO Auth by using SAML 2.0
+     *
+     * @return Filter chain proxy
+     * @throws Exception
+     */
     @Bean
     public FilterChainProxy samlFilter() throws Exception {
         List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
@@ -420,57 +426,67 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 samlIDPDiscovery()));
         return new FilterChainProxy(chains);
     }
-     
+
     /**
      * Returns the authentication manager currently used by Spring.
      * It represents a bean definition with the aim allow wiring from
      * other classes performing the Inversion of Control (IoC).
-     * 
-     * @throws  Exception 
+     *
+     * @throws Exception
      */
     @Bean
     @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }
-     
+
     /**
      * Defines the web based security configuration.
-     * 
-     * @param   http It allows configuring web based security for specific http requests.
-     * @throws  Exception 
+     *
+     * @param http It allows configuring web based security for specific http requests.
+     * @throws Exception
      */
-    @Override  
+    @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
-            .httpBasic()
+                .httpBasic()
                 .authenticationEntryPoint(samlEntryPoint());
         http
-        	.csrf()
-        		.disable();
+                .csrf()
+                .disable();
         http
-            .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
-            .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
-        
+                .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
+                .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
+
         http.authorizeRequests()
-        .antMatchers("/authenticate*/**").authenticated()
-        .anyRequest().permitAll();
-        
+                .antMatchers("/authenticate*/**").authenticated()
+                .anyRequest().permitAll();
+
         http
-            .logout()
+                .logout()
                 .logoutSuccessUrl("/");
     }
- 
+
     /**
      * Sets a custom authentication provider.
-     * 
-     * @param   auth SecurityBuilder used to create an AuthenticationManager.
-     * @throws  Exception 
+     *
+     * @param auth SecurityBuilder used to create an AuthenticationManager.
+     * @throws Exception
      */
     @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth
-            .authenticationProvider(samlAuthenticationProvider());
-    }   
+                .authenticationProvider(samlAuthenticationProvider());
+    }
+
+    @Bean
+    public Map<String, LGUser.Group> groupMappings(SamlGroupMapping mappings) {
+        return mappings.groupMappings();
+    }
+
+    @Bean
+    public Map<SamlKeyMappings.Key, String> keyMappings(SamlKeyMappings mappings) {
+        return mappings.mappings();
+    }
 
 }
\ No newline at end of file
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/Given.java b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
index 272ee36db013c224e86fb0226cd245fcd9d1cea9..6dc87eeb717e2c1707f1a50056f0ad706c6e0aff 100644
--- a/src/test/java/org/geant/lgservice/integration/scenario/Given.java
+++ b/src/test/java/org/geant/lgservice/integration/scenario/Given.java
@@ -21,9 +21,12 @@ import org.springframework.beans.factory.annotation.Autowired;
 
 import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
+import java.util.Collections;
 
 import static com.github.tomakehurst.wiremock.client.WireMock.get;
 import static org.geant.lgservice.integration.Context.withDefaults;
+import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
+import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
 import static org.mockito.ArgumentMatchers.any;
 
 @JGivenStage
@@ -59,23 +62,28 @@ public class Given extends ExtendedStage<Given> {
         }
 
         public GivenUser logged_in() {
-            context.withUser(new LGUser(
-                    "1",
-                    "barry",
-                    "scott",
-                    "barry.scott@geant.org",
-                    "GEANT Staff:All"));
+            context.withUser(
+                    LGUser.builder()
+                            .federatedUser("1")
+                            .givenName("barry")
+                            .surname("scott")
+                            .mail("barry.scott@geant.org")
+                            .group(INTERNAL)
+                            .group(EXTERNAL)
+                            .build());
             return self();
         }
 
         public GivenUser does_not_have_internal_access() {
             LGUser user = context.getUser();
-            context.withUser( new LGUser(
-                    user.getFederatedUser(),
-                    user.getGivenName(),
-                    user.getSurname(),
-                    user.getMail(),
-                    "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"));
+            context.withUser(
+                    LGUser.builder()
+                            .federatedUser(user.getFederatedUser())
+                            .givenName(user.getGivenName())
+                            .surname(user.getSurname())
+                            .mail(user.getMail())
+                            .group(EXTERNAL)
+                            .build());
             return self();
         }
     }
diff --git a/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
index 156fe3c887fd294ba63f20cd323a34395e6633dd..503caa603154bffa8984e78ad745a427b4a40e7d 100644
--- a/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
+++ b/src/test/java/org/geant/lgservice/integration/scenario/SubmitCommandRequestIT.java
@@ -80,7 +80,7 @@ public class SubmitCommandRequestIT extends BaseIT<Given, When, Then> {
         given()
                 .a().user()
                     .that().is().logged_in()
-                    .but().is().does_not_have_internal_access()
+                    .but().does_not_have_internal_access()
                     .backup()
                 .and().a().router()
                     .that().is().internal();
diff --git a/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java b/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..50f5332788d2478f5a67520d0ef15bee9a0735e6
--- /dev/null
+++ b/src/test/java/org/geant/lgservice/security/LGUserServiceTest.java
@@ -0,0 +1,71 @@
+package org.geant.lgservice.security;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.security.saml.SAMLCredential;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
+import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
+import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+
+public class LGUserServiceTest {
+
+    SAMLCredential credential = mock(SAMLCredential.class);
+
+    LGUserService subject = new LGUserService(
+            ImmutableMap.<SamlKeyMappings.Key, String>builder()
+                    .put(ID, "uid")
+                    .put(GIVEN_NAME, "givenName")
+                    .put(SURNAME, "sn")
+                    .put(EMAIL, "mail")
+                    .put(GROUPS, "isMemberOf")
+                    .build(),
+            ImmutableMap.<String, LGUser.Group>builder()
+                    .put("internal", INTERNAL)
+                    .put("external", EXTERNAL)
+                    .build());
+
+    @Before
+    public void setup() {
+        reset(credential);
+        when(credential.getAttributeAsString("uid")).thenReturn("1");
+        when(credential.getAttributeAsString("givenName")).thenReturn("barry");
+        when(credential.getAttributeAsString("sn")).thenReturn("scott");
+        when(credential.getAttributeAsString("mail")).thenReturn("barry.scott@geant.org");
+    }
+
+    @Test
+    public void loadUserWithMultipleGroups() {
+        when(credential.getAttributeAsStringArray("isMemberOf"))
+                .thenReturn(new String[]{"internal", "external", "ignore"});
+
+        LGUser result = (LGUser) subject.loadUserBySAML(credential);
+        assertThat(result.getFederatedUser()).isEqualTo("1");
+        assertThat(result.getGivenName()).isEqualTo("barry");
+        assertThat(result.getSurname()).isEqualTo("scott");
+        assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
+        assertThat(result.getAuthorities()).containsExactlyInAnyOrder(INTERNAL, EXTERNAL);
+    }
+
+    @Test
+    public void loadUser() {
+        when(credential.getAttributeAsStringArray("isMemberOf"))
+                .thenReturn(new String[]{"external", "ignore"});
+
+        LGUser result = (LGUser) subject.loadUserBySAML(credential);
+        assertThat(result.getFederatedUser()).isEqualTo("1");
+        assertThat(result.getGivenName()).isEqualTo("barry");
+        assertThat(result.getSurname()).isEqualTo("scott");
+        assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
+        assertThat(result.getAuthorities()).containsExactlyInAnyOrder(EXTERNAL);
+    }
+}
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index 3ceaa22a4b8c1e14e93d328803c1aaf7408735ab..4ff7a719f4e6ea3c6b147748b34a8c8113c8263a 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -17,3 +17,12 @@ saml:
     key-store: classpath:saml/keystore.jks
     key-alias: spring
     key-store-password: secret
+    key-mappings:
+      id: "id"
+      given-name: "givenName"
+      surname: "surname"
+      email: "mail"
+      groups: "isMemberOf"
+    group-mappings:
+      INTERNAL: "GEANT Staff:All"
+      EXTERNAL: "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"