Vue.js 자동로그인, 자동로그아웃

강정우·2023년 4월 6일
1

vue.js

목록 보기
36/72
post-thumbnail
post-custom-banner

새로고침

을 하면 원래 다 사라지는게 정상이다. 왜냐면 새로고침 === 새로운 vue앱 생성 이기 때문에 이전에 있던 vuex의 state도 새로 시작하는 것이 원래는 맞다. 하지만 30초 전에 로그인 한 정보가 새로고침 한다고 해서 다 날아가는 것은 정말 non sense이다. 그렇기에 이를 방지하고자 앞으로의 내용을 한 번 살펴보자.

localstorage

  • 사실 리액트 duckzil-pad를 작성하며 보안성때문에 토큰을 localstorage에 저장하면 매우 취약하다는 것을 알고있지만 서버와의 통신을 최소화한다는 의미와 프론트에서 해결한다는 의미이에서 한 번 작성해보겠다.

  • 우선 http는 async => actions => 여기를 작업해야겠다 라는 생각이 들어야한다.

export default {
    async login(context, payload) {
        return context.dispatch('auth',{
            ...payload,
            mode:'login'
        })
    },
    async signup(context, payload) {
        return context.dispatch('auth',{
            ...payload,
            mode:'signup'
        })
    },
    async auth(context, payload) {
        const mode = payload.mode;
        let url = '로그인 URL'
        if(mode==='signup'){
            url = '회원가입 URL'
        }
        const response = await fetch(url, {
            method: "POST",
            body:JSON.stringify({
                email: payload.email,
                password: payload.password,
                returnSecureToken:true
            })
        });
        const responseData = await response.json();
        if(!response.ok){
            const error = new Error(responseData.message || 'Failed to authenticate. Check ur login data')
            throw error;
        }
        localStorage.setItem('token', responseData.idToken);
        localStorage.setItem('userId', responseData.localId);

        context.commit('setUser',{
            token: responseData.idToken,
            userId: responseData.localId,
            tokenExpiration : responseData.expiresIn
        })
    },
    autoLogin(context) {
        const token = localStorage.getItem('token');
        const userId = localStorage.getItem('userId');

        if (token && userId) {
            context.commit('setUser',{
                token:token,
                userId:userId,
                tokenExpiration:null
            })
        }
    },
    logout(context){
        context.commit('setUser',{
            token : null,
            userId : null,
            tokenExpiration : null
        })

    }
}
  • 우선 중복되는 코드들을 auth(){} 로 refactoring 하였다.
    그 후 login, signup 메서드에 구조분해를 활용하여 mode만 이름을 부여하여 dispatch 하였다.

  • autoLogin 메서드에서는 로그인(auth(){})할 때 localStorage에 값들을 set 하였고 이 값들을 get하여 기존에 state(getter actually)에 값을 설정하도록 로직을 작성하였다.

    • 여기서 그치면 안 되고 vue life cycle을 이용하여 컴포넌트들이 렌더링(create)하기전에 해당 로직을 실행해야한다.

App.vue

<script>
export default {
	...
    created() {
        this.$store.dispatch('autoLogin')
    }
}
</script>
  • 이런식으로 action을 dispatch한다.

자동로그아웃

  • timer를 잘 활용한다.
let timer;
export default {
    async login(context, payload) {
        return context.dispatch('auth',{
            ...payload,
            mode:'login'
        })
    },
    async signup(context, payload) {
        return context.dispatch('auth',{
            ...payload,
            mode:'signup'
        })
    },
    async auth(context, payload) {
        const mode = payload.mode;
        let url = '로그인 URL'
        if(mode==='signup'){
            url = '회원가입 URL'
        }
        const response = await fetch(url, {
            method: "POST",
            body:JSON.stringify({
                email: payload.email,
                password: payload.password,
                returnSecureToken:true
            })
        });
        const responseData = await response.json();
        if(!response.ok){
            const error = new Error(responseData.message || 'Failed to authenticate. Check ur login data')
            throw error;
        }

        const expiresIn = +responseData.expiresIn *1000
        const expirationDate = new Date().getTime() + expiresIn;

        localStorage.setItem('token', responseData.idToken);
        localStorage.setItem('userId', responseData.localId);
        localStorage.setItem('tokenExpiration', expirationDate);

        timer = setTimeout(function () {
            context.dispatch('logout')
        }, expiresIn)

        context.commit('setUser',{
            token: responseData.idToken,
            userId: responseData.localId,
        })
    },
    autoLogin(context) {
        const token = localStorage.getItem('token');
        const userId = localStorage.getItem('userId');
        const tokenExpiration = localStorage.getItem('tokenExpiration')

        const expiresIn = +tokenExpiration - new Date().getTime();
        if(expiresIn<0){
            return
        }
        timer = setTimeout(function () {
            context.dispatch('logout')
        }, expiresIn);

        if (token && userId) {
            context.commit('setUser',{
                token:token,
                userId:userId,
            })
        }
    },
    logout(context){
        localStorage.removeItem('token');
        localStorage.removeItem('userId');
        localStorage.removeItem('tokenExpiration');
        clearTimeout(timer);
        context.commit('setUser',{
            token : null,
            userId : null,
        })

    }
}
  • 로그인할 때마다 생기는 토큰이 만료되는 미래 시각을 expirationDate로 저장하고

    • 요청을 전송하는 auth 액션으로 가서 localStorage에 데이터를 저장할 때 미래에 토큰이 만료되는 시각을 expirationDate로 계산하여 찾아보도록 한다.
    • 현재 시각에 토큰이 만료되기까지 소요되는 시간을 더해주면 된다.
    • 현재 타임스탬프를 알려 주는 new Date를 호출하여 현재 시각을 가지고 온다.
    • getTime을 통해 현재 타임스탬프를 밀리초로 나타내도록 하고 만료까지의 시간도 밀리초로 나타낸다.
  • 이를 위해 expiresIn를 추가하고 responseData.expiresIn에 1000을 곱하는 식으로 설정한다.

    • expiresIn은 초로 표시되기 때문에 1,000을 곱해 밀리초 값을 계산하고 이 expiresIn을 expirationDate에 더하여만료 시각 타임스탬프를 밀리초로 나타낼 수 있다.
  • setTimeout를 추가한 뒤 만료까지의 시간을 밀리초로 나타낸 expiresIn을 입력하고 이 시간이 지나면 함수가 실행되도록 설정하는데 이때 함수는 dispatch('logout')으로 토큰이 만료되었을 때 logout을 디스패치하도록 설정한다.

  • 또한 한 번에 하나의 타이머만 활성화될 수 있도록 전역 변수인 timer를 추가하고 timer = setTimeout으로 설정함으로써 본 파일 전역에서 timer 변수로 타이머를 삭제할 수 있다.

  • clearTimeout에서 timer를 입력하면 현재 진행 중인 타이머를 삭제할 수 있다.

    • 수동으로 Logout 버튼을 클릭했을 때 로그아웃 타이머를 제거하는 식.
  • 만료까지의 시간이 얼마나 남았는지를 확인하고 이를 바탕으로 새 타이머를 설정하도록 해 보자.

    • localStorage에 tokenExpiration를 저장했으니 tokenExpiration을 localStorage.getItem (tokenExpiration)에서 가지고 오면 미래 타임스탬프 값을 알 수 있다.
    • 이제 해당 타임스탬프와 현재 타임스탬프를 비교하여 상수인 expiresIn에 저장한 후 현재 시각을 빼 준다.(이 역시 밀리초로 나타낸 타임스탬프)
    • 이렇게 하면 토큰이 만료되는 미래 시각과 현재 시각의 차이를 계산할 수 있다.
    • 이때 expiresIn가 음수라면 해당 사용자를 로그인시킬 필요가 없다.
    • expiresIn가 0보다 작으면 당연히 토큰이 유효하지 않을 테니 타이머도 만료되고 사용자를 로그인시키지 않고 return으로 마무리 짓도록 한다.
    • 반대로 타이머가 양수인 경우 시간이 충분하고 토큰이 여전히 유효한 경우 지속 기간이 expiresIn인 타이머를 setTimeout으로 설정하여 위 타이머처럼 function이 실행되도록 설정한다.
    • context.dispatch('logout')를 호출하고 이 타이머를 그대로 timer 변수에 저장하도록 한다.
profile
智(지)! 德(덕)! 體(체)!
post-custom-banner

0개의 댓글