iltio

Make sure to register for free to get an app token to use the interface.

React

In it's most basic form the React plugin only requires an app token and an onSuccess callback so that something can happen once the user has authenticated.

import { Authentication } from 'iltio/react'

export const MyAuthentication = () => (
  <Authentication
    configuration={{ token: APP_TOKEN }}
    onSuccess={() => { redirect('/overview') }}
  />
)

The interactive example below can be used to try out and configure the authentication flow.

import { useState } from 'react'
import { Authentication } from 'iltio/react'

export default () => {
  const [user, setUser] = useState({})
  
  if (!user.token) {
    return (
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: 20, gap: 20 }}>
        <h1>Login and Registration Form Demo</h1>
        <Authentication
          configuration={{ token: 'demo' }}
          onSuccess={(name, token) => setUser({ name, token })}
        />
      </div>
    )
  }

  return (
    <div>
      <h1>Congratulations!</h1>
      <p>Logged in as {user.name}.</p>
      <button onClick={() => setUser({})}>Logout</button>
    </div>
  )
}

Form Customization

While the above is the most basic integration of the form there are many options to configure the form to seamlessly integrate with your application. Below is an example with all available properties and an example value.

import { Authentication } from 'iltio/react'

export const MyAuthentication = () => (
  <Authentication
    configuration={{ token: APP_TOKEN }}
    onSuccess={(name, token, registration) => { redirect('/overview') }}
    labels={{ submit: 'Login or Register' }}
    allowPhone={false}
    allowMail={true}
    Components={{ Button: MyButton, Input: MyInput, Form: MyForm, Tab: MyTab }}
    initialCountryCode="ch"
    variables={{
      color: 'blue',
      contrast: 'white',
      borderRadius: 10
    }}
    style={{
      form: { padding: 5 },
      inputMail: { background: 'red' },
      button: { cursor: 'pointer' },
      tab: { fontSize: 12 }
    }}
  />
)

All Form props are optional and the above values merely serve as examples.

import { useState } from 'react'
import { Authentication } from 'iltio/react'

const MyButton = ({ variables, style, ...props }) => {
  const [active, setActive] = useState(false)
  const [pressed, setPressed] = useState(false)

  return (
    <div
      style={{ display: 'flex', position: 'relative' }}
      className="my-button"
      onMouseEnter={() => setActive(true)}
      onMouseLeave={() => setActive(false)}
    >
      <span
        style={{
          position: 'absolute',
          inset: 0,
          background: 'black',
          borderRadius: 5,
          transition: 'all 0.2s ease-out 0s',
          transform: active ? 'translate(2px, 2px)' : 'none',
        }}
      />
      <button
        onMouseDown={() => setPressed(true)}
        onMouseUp={() => setPressed(false)}
        style={{
          display: 'flex',
          justifyContent: 'center',
          fontFamily: 'ui-serif, Georgia, serif',
          fontSize: 18,
          flex: 1,
          background: active
            ? 'linear-gradient(-45deg, hsl(209, 81.2%, 84.5%), hsl(210, 98.8%, 94.0%))'
            : 'black',
          border: '1px solid black',
          color: active ? 'black' : variables.contrast,
          padding: 10,
          cursor: 'pointer',
          borderRadius: 5,
          zIndex: 1,
          transform: pressed ? 'translateX(2px) translateY(2px)' : 'none',
          transition: 'all 0.2s ease-out 0s',
          ...style,
        }}
        {...props}
      />
    </div>
  )
}

const MyInput = ({ variables, style, valid = true, ...props }) => (
  <input
    style={{
      border: 'none',
      borderBottom: '1px solid black',
      borderColor: valid ? 'black' : 'red',
      paddingBottom: 5,
      color: variables.color,
      cursor: 'pointer',
      outline: 'none',
      backgroundColor: 'transparent',
      ...style,
    }}
    {...props}
  />
)

const MyPhoneWrapper = ({ variables, style, valid = true, ...props }) => (
  <div
    style={{
      borderBottom: '1px solid black',
      display: 'flex',
      flexDirection: 'column',
      borderColor: valid ? variables.color : 'red',
      ...style
    }}
    {...props}
  />
)

export default () => {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', padding: 20, gap: 20, backgroundColor: 'hsl(0, 0%, 93.0%)' }}>
      <h1>Customized Form Demo</h1>
      <Authentication
        configuration={{ token: 'demo' }}
        style={{ form: { width: 300, border: '1px dashed hsl(0, 0%, 78.0%)', padding: 20 }, tabWrapper: { color: 'hsl(0, 0%, 43.5%)' } }}
        variables={{ color: 'hsl(0, 0%, 43.5%)' }}
        Components={{
          Button: MyButton,
          Input: MyInput,
          PhoneWrapper: MyPhoneWrapper
        }}
      />
    </div>
  )
}

Here are all the available options for the more complex properties.

export default () => (
  <Authentication
    labels={{
      submit?: string, // Label of the submit button.
      tabPhone?: string, // Tab phone label text.
      tabMail?: string, // Tab mail label text.
      resend?: string, // Resend code button label.
    }}
    variables={{
      color?: string, // General color used.
      colorError?: string, // Color used to highlight erroneous parts.
      contrast?: string, // Contrast color for dark backgrounds (button).
      borderRadius?: number | string, // Border radius of various elements.
      smallSpace?: number | string, // Smaller space used.
      space?: number | string, // Larger space used.
      fontSize?: number | string, // General font size.
      fontFamily?: string, // General font family.
    }}
    style={{
      form?: CSSProperties, // Wrapper element.
      tabWrapper?: CSSProperties, // Wrapper for tabs (if multiple methods).
      tab?: CSSProperties,
      inputMail?: CSSProperties,
      inputCode?: CSSProperties, // Verification code input in step 2.
      button?: CSSProperties, // Generic button.
      error?: CSSProperties, // Error message text.
      message?: CSSProperties, // Information message text.
      // Various styles to configure the phone number input styles.
      phoneWrapper?: CSSProperties,
      phoneTop?: CSSProperties,
      phoneCountry?: { button?: CSSProperties; flag?: CSSProperties; prefix?: CSSProperties },
      phoneInput?: CSSProperties,
      phoneCountryOptions?: CSSProperties,
      phoneCountryOption?: { button?: CSSProperties; text?: CSSProperties },
      phoneInputCountrySearch?: CSSProperties,
    }}
    Components={{
      Form?: FunctionComponent, // Wrapper element.
      TabWrapper?: FunctionComponent,
      Tab?: FunctionComponent,
      Input?: FunctionComponent, // Input used for mail, phone and code.
      Button?: FunctionComponent, // Submit and resend button.
      Error?: FunctionComponent, // Error message element.
      Message?: FunctionComponent, // Information message element.
      // Various elements for the phone number input.
      PhoneWrapper?: FunctionComponent,
      PhoneInput?: FunctionComponent,
      PhoneTop?: FunctionComponent,
      PhoneCountry?: FunctionComponent,
      PhoneCountryOptions?: FunctionComponent,
      PhoneCountryOption?: FunctionComponent,
    }}
    configuration={{
      url?: string,
      token: string,
      storage?: BasicStorage,
      authenticateUrl?: string,
      tokenStorageKey?: string,
      codeTokenStorageKey?: string,
      nameStorageKey?: string,
      pollDuration?: number,
    }}
  />
)

Most function components will receive the variables, style: CSSProperties and the children. The input element also receives a valid attribute that should be used to style the input accordingly when invalid.

Configuration

A separate configure method is used so that the configuration can be shared between the framework component and the various helper methods. If only the Authentication is used the same configuration can be passed there directly using the configuration property.

import { MemoryStorage, configure } from 'iltio'

configure({
  token: APP_TOKEN,
  storage?: MemoryStorage,
  url?: 'https://my-server.com/api',
  authenticateUrl?: 'https://my-server.com/api/authenticate',
  pollDuration?: 5000,
  tokenStorageKey?: 'auth-token',
  codeTokenStorageKey?: 'auth-verify-token',
  nameStorageKey?: 'auth-name',
})

In order for the authentication to work a token is always required. By default the token will be stored inside window.sessionStorage. If access is denied MemoryStorage will be used as a fallback. Any method compatible with the Storage interface including the more permanent window.localStorage can be used. It's possible to route any requests through your own server by setting a different url. When the goal is to only keep the APP_TOKEN private it's possible to reroute only the first request with authenticateUrl. During the verification stage the interval to poll for verification through link can be set in milliseconds as pollDuration. When using a key based storage like localStorage all of the address keys can be configured as well.

React Native

This plugin wraps the React version with custom UI components and compatible storage interface. Due to this most of the configurations described above also apply here. The style property differs for some components as they are made up of more elements. For full customization of the UI of any part the Components property can be used. To override a component it's best to use the default implementation as a starting point as it shows the available properties. This plugin also requires react-native-localize to be installed.

import { Authentication, NativeStyles } from 'iltio/native' // Requires "unstable_enablePackageExports" in metro configuration.
import { Authentication, NativeStyles } from 'iltio/dist/native/index.js' // Fallback without exports.

export const MyNativeAuthentication = () => (
  <Authentication
    configuration={{ token: APP_TOKEN }}
    onSuccess={(name, token) => { go('Overview') }}
    style={{
      button: { text: { color: 'blue' }},
      phoneCountryOption: { touchable: { backgroundColor: 'red' }}
    }}
    Components={{
      Error: ({ style, variables, children, ...props }) => (
        <Text style={{ backgroundColor: 'red', marginBottom: variables.smallSpace, ...style }} {...props}>
          {children}
        </Text>
      )
    }}
  />
)

When importing as iltio/native make sure to set unstable_enablePackageExports to true in the "metro": { "resolver": { ... } } field in package.json or in a dedicated configuration file as seen in metro.config.js used by the plugin. This will require at least React Native 0.72 and will become the default behaviour in 0.73.

All the available style properties are defined in the NativeStyles type exported by the plugin. Components are specified in the ComponentTypes type exported as well.

import React, { useMemo } from 'react'
import {
  SafeAreaView,
  StyleSheet,
  Text,
  View,
  Alert,
  TextInput,
  ScrollView,
  TouchableOpacity,
  Platform,
} from 'react-native'
import { Authentication } from 'iltio/native'

const styles = StyleSheet.create({
  wrapper: {
    marginHorizontal: 20,
    marginVertical: 20,
    flex: 1,
  },
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    textAlign: 'center',
    fontWeight: 'bold',
    fontSize: 20,
  },
  description: {
    fontWeight: 'bold',
    fontSize: 16,
    marginVertical: 20,
  },
  inputElement: {
    padding: 0, // Android
  },
})

const inputWrapperStyles = (variables: any) => ({
  borderWidth: 0,
  borderBottomWidth: 1,
  borderColor: variables.color,
  borderRadius: variables.borderRadius,
  marginBottom: variables.space,
  paddingVertical: variables.smallSpace,
})

const CustomInput = ({ variables, style, onChange, type, ...props }: any) => (
  <View style={inputWrapperStyles(variables)}>
    <TextInput
      onChangeText={(value: string) => onChange({ target: { value } })}
      style={{
        ...styles.inputElement,
        ...style,
      }}
      keyboardType="email-address"
      autoCapitalize="none"
      autoCorrect={false}
      {...props}
    />
  </View>
)

const CustomButton = ({ variables, style, onClick, children, ...props }: any) => (
  <TouchableOpacity
    style={{
      backgroundColor: variables.color,
      borderRadius: 5,
      ...style,
    }}
    {...props}
    onPress={onClick}
  >
    <Text
      style={{
        textAlign: 'center',
        color: variables.contrast,
        padding: variables.smallSpace,
        borderRadius: variables.borderRadius,
        fontSize: 18,
        fontWeight: 'bold',
        fontFamily: Platform.OS === 'android' ? 'serif' : 'Times New Roman',
      }}
    >
      {children}
    </Text>
  </TouchableOpacity>
)

const CustomPhoneWrapper = ({ style, variables, ...props }: any) => (
  <View
    style={{
      flexDirection: 'column',
      borderWidth: 0,
      borderColor: variables.color,
      borderRadius: variables.borderRadius,
      marginBottom: variables.space,
      ...style,
    }}
    {...props}
  />
)

const CustomPhoneTop = ({ style, variables, ...props }: any) => (
  <View
    style={{
      flexDirection: 'row',
      borderBottomWidth: 1,
      borderColor: variables.color,
      paddingVertical: variables.smallSpace,
    }}
    {...props}
  />
)

const handleSuccess = (_: string, name: string) =>
  Alert.alert(`Successfully authenticated ${name}.`)

export default () => {
  return (
    <SafeAreaView style={styles.wrapper}>
      <Text style={styles.title}>iltio Native Authentication</Text>
      <ScrollView contentContainerStyle={styles.container}>
        <Text style={styles.description}>Without Customization</Text>
        <Authentication onSuccess={handleSuccess} />
        <Text style={styles.description}>Various Customizations</Text>
        <Authentication
          allowMail={false}
          variables={{ color: 'blue', borderRadius: 10 }}
          labels={{ submit: 'Register or Login' }}
          onSuccess={handleSuccess}
        />
        <Text style={styles.description}>Custom UI Components</Text>
        <Authentication
          configuration={{ token: 'demo' }}
          onSuccess={handleSuccess}
          Components={{
            Input: CustomInput,
            Button: CustomButton,
            PhoneWrapper: CustomPhoneWrapper,
            PhoneTop: CustomPhoneTop,
          }}
        />
      </ScrollView>
    </SafeAreaView>
  )
}

The above demo shows three different ways to use and customize the React Native plugin. For this web demo it's using react-native-web to render the native UI on the web.

Native Storage

It's best to use the existing persistent storage solution your app is already using. Do this by only displaying the Form if there is no token yet and store the token from the onSuccess callback once the user has successfully authenticated.

The storage interface provided by this plugin will also store the token, as well as the intermediate token for step two to continue there in case the user has left the app. Unlike on the web, with a refresh the data in memory for an app will stay there and there is usually no need for persistent storage. Still, if there is a desire to use the existing storage interface in a persistent way, here is how to do this:

import { Authentication, Store } from 'iltio/native'
import AsyncStorage from '@react-native-async-storage/async-storage'

const readToken = () => Store.token

export const MyNativeAuthentication = () => (
  <Authentication
    configuration={{ token: APP_TOKEN, storage: AsyncStorage }}
    onSuccess={() => {}}
  />
)

See details to install AsyncStorage on this link. If you want to make sure the token is stored in an encrypred way consider using react-native-encrypted-storage.

Vue

<script>
import Authentication from 'iltio/vue'
</script>

<template>
  <Authentication
    :configuration="{ token: 'demo' }"
    :on-success="(name) => console.log(name)"
  />
</template>

The Vue plugin supports the same configurations and customizations as the React version described above in more detail. Any UI component can be overrideen with a custom Vue component.

<script>
import Authentication from 'iltio/vue'
import { MyButton } from './MyButton.vue'
</script>

<template>
  <Authentication
    :style="{
      form: { width: '300px', border: '1px dashed hsl(0, 0%, 78.0%)', padding: '20px' },
    }"
    :variables="{ color: 'hsl(0, 0%, 43.5%)', contrast: 'red' }"
    :Components="{
      Button: MyButton,
    }"
  />
</template>

Svelte

<script>
  import Authentication from 'iltio/svelte'
</script>

<Authentication
  configuration={{ token: 'demo' }}
  onSuccess={(name) => console.log(name)}
/>

The Svelte plugin supports the same configurations and customizations as the React version described above in more detail. Any UI component can be overrideen with a custom Svelte component. style requires strings and not objects to configure the inline-styles.

<script>
  import Authentication from 'iltio/svelte'
  import { MyInput } from './components/MyInput.svelte'
</script>

<Authentication
  style={{
    form: 'width: 300px; border: 1px dashed hsl(0, 0%, 78.0%); padding: 20px;',
  }}
  variables={{ color: 'blue' }}
  Components={{
    Input: MyInput,
  }}
/>

JavaScript 🚧 Coming Soon

With the helpers provided below it should be fairly easy to integrate the login manually into any framework. The following javaScriptForm method provides a legacy solution to create a form that that will provide the full authentication functionality.

import { javaScriptForm, configure } from 'iltio'

configure({ token: APP_TOKEN })

javaScriptForm('#my-authentication')

Helper Methods

The following methods can be helpful before or after the authentication flow as well as when building a custom integration in JavaScript.

Store

import { Store } from 'iltio'

// Reads the token from the assigned storage method.
Store.token

// Manually set the token.
Store.token = USER_TOKEN

// Remove the token.
Store.removeToken()

// Similarly, read, write or remove the name.
Store.name

// Similarly, read, write or remove the code token.
Store.codeToken

The token will be set when the user successfully logs in. The codeToken on the other hand is set after the first step and will ensure the user continues at the verification step even when the page is accidentially refreshed.

getNameType

import { getNameType } from 'iltio'

getNameType('test@testing.com') => 'mail'
getNameType('+41799629162') => 'phone'

authenticate

import { authenticate } from 'iltio'

// authenticate(name: string)
const { error, codeToken, registration } = await authenticate('test@testing.com')

poll

import { poll } from 'iltio'

// poll()
const { error, token } = await poll()

confirm

import { confirm } from 'iltio'

// confirm(code: string)
const { error, token } = await confirm('1234')

resendCode

import { resend } from 'iltio'

// resend(token = Store.codeToken)
const { error, token } = await resend()

authorize

import { authorize } from 'iltio'

// authorize(token = Store.token)
const { error, role, id, name } = await authorize()

logout

import { logout } from 'iltio'

// logout(server = false, token = Store.token)
const { error } = await logout(true)

remove

import { remove } from 'iltio'

// remove(token = Store.token)
const { error } = await remove()

Continue Reading

Interface