Examples of Netlify Identity w/o the widget

I have a site using Netlify Identity and netlify-identity-widget. We’ve found some issues with how it works in Safari, and from what I can see on the repo, it looks like the widget hasn’t been updated recently. We are looking at just building our own UI and using gotrue direct, but was curious if anyone had an example of this they could share.

I have an example, but you might not like it. Setting up Identity without the Identity Widget is a task, especially since the Identity Endpoints are not documented. In the past, I was working on a project to try implementing my own logic and had got successful for the most part, but I haven’t completed it. Here’s the example using Alpine:

import Alpine from 'alpinejs'
import Axios from 'axios'
function onLoad() {
  window.removeEventListener('load', onLoad)
  Alpine.data('alpine', () => ({
    auth: {
      axios: Axios.create({
        baseURL: 'https://site.netlify.app/.netlify/identity',
        headers: {
          'x-use-cookie': 1
        },
        method: 'post'
      }),
      email: null,
      error: null,
      loading: false,
      login() {
        this.auth.loading = true
        this.auth.text = 'Logging in...'
        this.auth.axios({
          data: `grant_type=password&password=${encodeURIComponent(this.auth.password)}&username=${encodeURIComponent(this.auth.email)}`,
          url: '/token'
        }).then(({data}) => {
          this.auth.save(data).then(() => {
            this.auth.loading = false
            this.auth.text = 'Successfully logged in! Redirecting...'
            this.auth.reload()
          })
        }).catch(({response: {data: {error_description}, status}}) => {
          this.auth.error = {
            message: `"${error_description}"`,
            status
          }
          this.auth.loading = false
          this.auth.text = null
        })
      },
      logout() {
        this.auth.loading = true
        this.auth.text = 'Logging out...'
        this.auth.axios({
          headers: {
            authorization: `Bearer ${JSON.parse(localStorage.getItem('user')).token.access_token}`
          },
          url: '/logout'
        }).then(() => {
          localStorage.removeItem('user')
          this.auth.loading = false
          this.auth.text = 'Successfully logged out!'
          location.href = '/'
        }).catch(() => {
          this.auth.loading = false
          this.auth.text = null
        })
      },
      password: null,
      passwordVisible: false,
      recover() {
        this.auth.loading = true
        this.auth.text = 'Sending password reset e-mail...'
        this.auth.axios({
          data: {
            email: this.auth.email
          },
          url: '/recover'
        }).then(() => {
          this.auth.loading = false
          this.auth.text = 'Password reset e-mail successfully sent!'
        }).catch(({response: {data: {msg}, status}}) => {
          this.auth.error = {
            message: `"${msg}"`,
            status
          }
          this.auth.loading = false
          this.auth.text = null
        })
      },
      reload() {
        if (location.pathname === '/') {
          location.href = '/path/'
        } else {
          location.reload()
        }
      },
      refresh() {
        this.loading = true
        this.text = 'Refreshing JWT...'
        this.axios({
          data: `grant_type=refresh_token&refresh_token=${JSON.parse(localStorage.getItem('user')).token.refresh_token}`,
          url: '/token'
        }).then(({data}) => {
          this.save(data).then(() => {
            this.loading = false
            this.text = 'JWT successfully refreshed! Redirecting...'
            this.reload()
          })
        }).catch(({response: {data: {error_description}, status}}) => {
          this.error = {
            message: `"${error_description}"`,
            status
          }
          localStorage.removeItem('user')
          this.loading = false
        })
      },
      save(token) {
        return new Promise((resolve, reject) => {
          this.axios({
            headers: {
              authorization: `Bearer ${token.access_token}`
            },
            method: 'get',
            url: '/user'
          }).then(({data}) => {
            localStorage.setItem('user', JSON.stringify({
              ...data,
              token: {
                ...token,
                expires_at: Date.now() + 3000000,
                expires_in: 3000
              },
              url: this.axios.defaults.baseURL
            }))
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
      signup() {
        this.auth.loading = true
        this.auth.text = 'Signing up...'
        this.auth.axios({
          data: {
            password: this.auth.password,
            token: this.auth.token,
            type: 'signup'
          },
          url: '/verify'
        }).then(({data}) => {
          this.auth.token = null
          this.auth.save(data).then(() => {
            this.auth.loading = false
            this.auth.text = 'Successfully signed up! Redirecting...'
            this.auth.reload()
          })
        }).catch(({response: {data: {msg}, status}}) => {
          this.auth.error = {
            message: `"${msg}"`,
            status
          }
          this.auth.token = null
          this.auth.loading = false
        })
      },
      update() {
        this.auth.loading = true
        this.auth.text = 'Updating password...'
        this.auth.axios({
          data: {
            password: this.auth.password
          },
          headers: {
            authorization: `Bearer ${JSON.parse(localStorage.getItem('user')).token.access_token}`
          },
          method: 'put',
          url: '/user'
        }).then(() => {
          this.auth.token = null
          this.auth.loading = false
          this.auth.text = 'Password successfully updated! Redirecting...'
          this.auth.reload()
        }).catch(({response: {data: {msg}, status}}) => {
          this.auth.error = {
            message: `"${msg}"`,
            status
          }
          this.auth.token = null
          this.auth.loading = false
        })
      },
      tab: 'login',
      text: null,
      token: null
    },
    init() {
      if (location.hash.includes('#invite_token=')) {
        this.auth.tab = 'signup'
        this.auth.token = location.hash.slice(14)
        history.replaceState(null, null, location.origin)
      } else if (location.hash.includes('#recovery_token=')) {
        this.auth.tab = 'update'
        this.auth.token = location.hash.slice(16)
        history.replaceState(null, null, location.origin)
        this.auth.loading = true
        this.auth.text = 'Verifying recovery token...'
        this.auth.axios({
          data: {
            token: this.auth.token,
            type: 'recovery'
          },
          url: '/verify'
        }).then(({data}) => {
          this.auth.save(data).then(() => {
            this.auth.loading = false
            this.auth.text = 'Recovery token verified successfully!'
            this.auth.tab = 'update'
          })
        }).catch(({response: {data: {msg}, status}}) => {
          this.auth.error = {
            message: `"${msg}"`,
            status
          }
          this.auth.loading = false
          this.auth.text = null
        })
      } else if (localStorage.getItem('user')) {
        if (Date.now() > JSON.parse(localStorage.getItem('user')).token.expires_at) {
          this.auth.refresh()
        } else {
          location.href = '/some-path/'
        }
      }
      this.$watch('auth.text', newValue => {
        if (newValue) {
          setTimeout(() => {
            this.auth.text = null
          }, 2500)
        }
      })
    }
  }))
  Alpine.start()
}
window.addEventListener('load', onLoad)

I wanted to release this as an example for self-managing Identity stuff, but due to lack of demand, and change of priorities, I never completed it.

I might not be remembering the code very well, but I can answer some questions you might have.