Auth0 Home Blog Docs

Prevent logging in every 2 hours

lock-11

#1

I have read many articles about this and remain confused. I am using Vue/Nuxt/SPA and Lock 11.

Is there a definitive article on how to refresh tokens without forcing the user to login in every 2 hours? There is so much information on this, a lot of it conflicting, that I thought I’d try here before slogging on myself.

Thanks!


#2

Hi @3bfred

You can use Silent Authentication to get new tokens for the user without using a refresh token nor having to show them a login page.

Hope this helps!


#3

Thanks @charsleysa. Looks promising. I’ll give it a go.


#4

Hey @3bfred!

Let us know once you implement it if it worked for you!


#5

@konrad.sopala - I have just spent 6 hours not making any progress. I am nearly at the point where I am going to have to look for another solution. I am sure that it is easy to solve but I have absolutely no ideas left. The relevant code I am using is below.

Essentially, all I need to do is ensure that the user only has to log in every week or month.

Thanks


import Auth0Lock from 'auth0-lock'
import jwtDecode from 'jwt-decode'
import queryString from 'query-string'
import config from '~/modules/config.js'

const UPDATE_SESSION_MINUTES = config.auth0.updateSessionMinutes || 5

class Auth0Util {
  constructor(context) {
    this.context = context
  }

  getLock(container) {
    return new Auth0Lock(config.auth0.clientID, config.auth0.domain, {
      container,
      closable: false,
      rememberLastLogin: false,
      auth: {
        responseType: 'token id_token',
        redirectUrl: this.getBaseUrl() + '/user/callback',
        params: {
          scope: 'openid profile email'
        },
        prompt: 'none'
      }
    })
  }

  showLock(container) {
    this.getLock(container).show()
  }

  updateTokensIfNecessary() {
    // This is called on every route change
    // If it is not within USM minutes of expiry, no need to do anything
    let test = 'aaa'
    if (test === 'aaa') {
      if (
        new Date().getTime() <
        +this.getExpiresAt() - UPDATE_SESSION_MINUTES * 60 * 1000
      ) {
        return
      }
    }

    // Get a new session key
    const lock = this.getLock()

    lock.checkSession(
      { scope: 'openid profile email', prompt: 'none' },
      (error, authResult) => {
        if (error || !authResult) {
          lock.show()
        } else {
          const { accessToken, idToken, expiresIn } = authResult

          this.setToken({
            access_token: accessToken,
            id_token: idToken,
            expires_in: expiresIn
          })

          console.log('TOKEN UPDATED')
        }
      }
    )
  }

  logout() {
    this.unsetToken()
    this.getLock().logout({ returnTo: '/' })
  }

  getBaseUrl() {
    return `${window.location.protocol}//${window.location.host}`
  }

  getQueryParams() {
    return queryString.parse(location.hash)
  }

  setToken({ access_token, id_token, expires_in }) {
    window.localStorage.setItem('accessToken', access_token)
    window.localStorage.setItem('idToken', id_token)
    window.localStorage.setItem(
      'expiresAt',
      expires_in * 1000 + new Date().getTime()
    )
    window.localStorage.setItem('user', JSON.stringify(jwtDecode(id_token)))
  }

  setTokenByQuery() {
    this.setToken(this.getQueryParams())
  }

  isAuthenticated() {
    const expiresAt = window.localStorage.getItem('expiresAt')
    return new Date().getTime() < expiresAt
  }

  unsetToken() {
    window.localStorage.removeItem('accessToken')
    window.localStorage.removeItem('idToken')
    window.localStorage.removeItem('expiresAt')
    window.localStorage.removeItem('user')
  }

  getExpiresAt() {
    return localStorage.getItem('expiresAt')
  }

  getIdToken() {
    return this.isAuthenticated() ? localStorage.getItem('idToken') : null
  }

  getAccessToken() {
    return this.isAuthenticated() ? localStorage.getItem('accessToken') : null
  }

  getUser() {
    return this.isAuthenticated()
      ? JSON.parse(localStorage.getItem('user'))
      : null
  }
}

export default (context, inject) => {
  inject('auth0', new Auth0Util(context))
}

#6

Hi @3bfred

From what I can see that code looks fine though I would note that anytime you’re about to show the lock you should first do call to .checkSession() to see if the user has a valid Auth0 SSO session.

The reasoning for that is because your SPA may have expired tokens but there is still a valid Auth0 SSO so if you don’t call .checkSession() first and instead show the lock you won’t be taking advantage of the Auth0 SSO session.

If you’ve checked everything and believe it’s all in the right order then post (or DM) a .HAR file (with sensitive information obfuscated) and we’ll be able to further diagnose the issue.


#7

@charsleysa Thanks a lot for your response. I’ve made this change and am testing this out today.


#8

@charsleysa - still got problems. I know I have a valid session at the point I am calling checkSession. I previously logged in and then called the code you see below directly.

However, checSession is returning an error ‘login_required’ and immediately triggers another login. I can see that this login is calling setToken().

So basically, I get into an endless loop of the lock auth dialog appearing.

Freddy


#9

Hi @3bfred

Are you able to post (or DM) the logs from the Auth0 management dashboard for the failed check session (AKA silent authentication) requests with sensitive information obfuscated?

They should hopefully provide a bit more insight as to the reason why you are seeing a login_required error.


#10

@charsleysa - here are the logs …

{
  "date": "2019-01-13T12:36:00.988Z",
  "type": "fsa",
  "description": "Login required",
  "client_id": "xxx,
  "client_name": "Bread",
  "ip": "86.174.33.179",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
  "details": {
    "body": {},
    "qs": {
      "client_id": "xxx",
      "response_type": "token id_token",
      "redirect_uri": "http://localhost:4000/user/callback",
      "scope": "openid profile email",
      "state": "mUwVy.ODVO3AkjgFFjhhMSe.sTVD0l7K",
      "nonce": "0z-sCYrdKcUiLMM3DPto32~jn6U36y9y",
      "response_mode": "web_message",
      "prompt": "none",
      "auth0Client": "eyJuYW1lIjoibG9jay5qcyIsInZlcnNpb24iOiIxMS4xMi4xIiwibGliX3ZlcnNpb24iOnsicmF3IjoiOS44LjIifX0="
    },
    "connection": null,
    "error": {
      "message": "Login required",
      "oauthError": "login_required",
      "type": "oauth-authorization"
    }
  },
  "hostname": "breadbasket.eu.auth0.com",
  "audience": "https://breadbasket.eu.auth0.com/userinfo",
  "scope": [
    "openid",
    "profile",
    "email"
  ],
  "log_id": "900201901131236009887927558070895a66860722966744885362690"
}

and context data …

{
  "body": {},
  "qs": {
    "client_id": "xxx",
    "response_type": "token id_token",
    "redirect_uri": "http://localhost:4000/user/callback",
    "scope": "openid profile email",
    "state": "mUwVy.ODVO3AkjgFFjhhMSe.sTVD0l7K",
    "nonce": "0z-sCYrdKcUiLMM3DPto32~jn6U36y9y",
    "response_mode": "web_message",
    "prompt": "none",
    "auth0Client": "eyJuYW1lIjoibG9jay5qcyIsInZlcnNpb24iOiIxMS4xMi4xIiwibGliX3ZlcnNpb24iOnsicmF3IjoiOS44LjIifX0="
  },
  "connection": null,
  "error": {
    "message": "Login required",
    "oauthError": "login_required",
    "type": "oauth-authorization"
  }
}

Thanks for helping out.


#11

Hi @3bfred

I noticed you are testing using localhost, I think this may be possibly the reason it’s not working for you as I know there are restrictions when using localhost (for security purposes) and I think the silent authentication may be one of them.

In our organization we have setup a local IP alias record to circumvent these restrictions (so a dev.mydomain.com A record with the value 127.0.0.1).

I would recommend trying to setup an alias record (or modifying your operating system’s host file but this method is less favorable) and see if you still have the same error.

Another thing to note is there are also restrictions when using Social login (such as Google) when you haven’t setup your own keys and are using the Auth0 development keys.


#12

Many thanks @charsleysa for the quick response. I will check it out on a staging server that is not localhost and not use localhost on my dev machine. I’ll also set up the proper development keys.

Hopefully edging closer to a solution :slight_smile: