diff --git a/gui/src/components/search/SearchContext.js b/gui/src/components/search/SearchContext.js
index 9c11f1c59520fac79a5e1c952cd81f6a12ea0422..8b3ee59e2146b0d6c3a0799c1d9fd0ea569505e2 100644
--- a/gui/src/components/search/SearchContext.js
+++ b/gui/src/components/search/SearchContext.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-unused-vars */
 /*
  * Copyright The NOMAD Authors.
  *
@@ -98,6 +97,16 @@ function parseQueryString(queryString) {
     : undefined
 }
 
+/**
+ * Removes keycloak-specific fragments from the given URL.
+*/
+function removeKeycloak(url) {
+  for (const pattern of ['#state=', '#iss=', '#error=']) {
+    url = url.split(pattern)[0]
+  }
+  return url
+}
+
 const RouteListener = React.memo(() => {
   const location = useLocation()
   const history = useHistory()
@@ -119,7 +128,7 @@ const RouteListener = React.memo(() => {
     // the syntax for data within custom schemas uses a hashtag. TODO: Keycloak
     // seems to use the hashtag for special purposes as well, which causes some
     // clashes when loading the page from scratch.
-    const queryString = location.search.slice(1) + location.hash.startsWith("#state=") ? '' : location.hash
+    const queryString = removeKeycloak(location.search.slice(1) + location.hash)
     // Empty query string preserves the old filters, as this will enable people
     // to preserve filters when changing apps. This causes minor
     // inconsistencies, but might be better for the UX.
@@ -143,13 +152,15 @@ export const withQueryString = (WrappedComponent) => {
     const firstRender = useRef(true)
     const valueRef = useRef()
 
+    // Extract URL query parameters, ignore keycloak authentication state
+    // parameters starting with '#iss='.
     useMemo(() => {
       if (firstRender.current) {
         const split = window.location.href.split('?')
-        const search = (split.length !== 1)
-          ? split.pop()
+        const search = (split.length > 1)
+          ? parseQueryString(removeKeycloak(split.slice(-1)[0]))
           : {}
-        valueRef.current = {...(initialFilterValues || {}), ...parseQueryString(search)}
+        valueRef.current = {...(initialFilterValues || {}), ...search}
         firstRender.current = false
       }
     }, [initialFilterValues])
diff --git a/gui/src/components/search/SearchContext.spec.js b/gui/src/components/search/SearchContext.spec.js
index a98aaac87dcefd1f380c0e25aaa75ac041814a0f..46740c663efb8da2bf8744f1b8a9334fb925a910 100644
--- a/gui/src/components/search/SearchContext.spec.js
+++ b/gui/src/components/search/SearchContext.spec.js
@@ -21,6 +21,7 @@ import { useSearchContext } from './SearchContext'
 import { Quantity } from '../units/Quantity'
 import { isEqualWith } from 'lodash'
 import { SearchSuggestion, SuggestionType } from './SearchSuggestion'
+import { WrapperDefault } from '../conftest.spec'
 
 describe('parseQuery', function() {
   test.each([
@@ -82,3 +83,29 @@ describe('suggestions', function() {
     expect(suggestions[0].key).toBe(newSuggestion.key)
   })
 })
+
+describe('reading query from URL', function() {
+  test.each([
+    ['no query parameters', '', {}],
+    ['query parameter', '?upload_id=testing', {upload_id: new Set(['testing'])}],
+    ['query parameter with keycloak iss', '?upload_id=testing#iss=https://nomad-lab.eu/fairdi/keycloak/auth/realms/fairdi_nomad_prod', {upload_id: new Set(['testing'])}],
+    ['query parameter with keycloak error', '?upload_id=testing#error=login_required', {upload_id: new Set(['testing'])}],
+    ['query parameter with keycloak state', '?upload_id=testing#state=here is a state', {upload_id: new Set(['testing'])}]
+  ])('%s', async (name, params, expected_query) => {
+    // Set window.location.href
+    // eslint-disable-next-line no-global-assign
+    window = Object.create(window)
+    const url = "http://testing.com" + params
+    Object.defineProperty(window, 'location', {
+      value: {href: url},
+      writable: true
+    })
+
+    // Call hooks to check that query is correctly read
+    const { result: resultUseSearchContext } = renderHook(() => useSearchContext(), { wrapper: WrapperSearch })
+    const { result: resultUseQuery } = renderHook(() => resultUseSearchContext.current.useQuery(), { wrapper: WrapperDefault})
+    const query = resultUseQuery.current
+    console.log(query)
+    expect(query).toMatchObject(expected_query)
+  })
+})