Added sign up button and improved logout flow.
Also added proper link to profile management.
This commit is contained in:
		
							
								
								
									
										13
									
								
								MultiShop/client/public/authentication/callback-handler.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								MultiShop/client/public/authentication/callback-handler.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { UserManager, WebStorageStateStore } from "oidc-client";
 | 
			
		||||
 | 
			
		||||
const userManager = new UserManager({
 | 
			
		||||
    authority: window.location.origin,
 | 
			
		||||
    client_id: "MultiShop",
 | 
			
		||||
    redirect_uri: window.location.origin + "/authentication/login-callback",
 | 
			
		||||
    post_logout_redirect_uri: window.location.origin + "/authentication/logout-callback",
 | 
			
		||||
    response_type: "code",
 | 
			
		||||
    scope: "openid profile",
 | 
			
		||||
    userStore: new WebStorageStateStore({ store: window.localStorage }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
userManager.signinSilentCallback();
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
<!-- Completely separate static html should improve silent login performance as we don't need to load entire SPA.  -->
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
    <title>authentication</title>
 | 
			
		||||
    <script type="module" src="callback-handler.js"></script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    <div>
 | 
			
		||||
        Silently authenticating user. If you are seeing this page, you probably want to <a href="/">go back to the app</a>.
 | 
			
		||||
    </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -35,6 +35,10 @@
 | 
			
		||||
                            <ProfileDisplay>
 | 
			
		||||
                            </ProfileDisplay>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                            <ProfileSignUp>
 | 
			
		||||
                            </ProfileSignUp>
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li class="nav-item">
 | 
			
		||||
                            <ProfileLogIn>
 | 
			
		||||
                            </ProfileLogIn>
 | 
			
		||||
@@ -57,14 +61,17 @@ import "./assets/scss/main.scss";
 | 
			
		||||
import ProfileDisplay from "./components/ProfileDisplay.vue";
 | 
			
		||||
import ProfileLogIn from "./components/ProfileLogIn.vue";
 | 
			
		||||
import ProfileLogOut from "./components/ProfileLogOut.vue";
 | 
			
		||||
import ProfileSignUp from "./components/ProfileSignUp.vue";
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        ProfileDisplay,
 | 
			
		||||
        ProfileLogIn,
 | 
			
		||||
        ProfileSignUp,
 | 
			
		||||
        ProfileLogOut
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        this.$store.dispatch("attemptSilentAuthentication");
 | 
			
		||||
        this.$store.dispatch("updatePublicApiSettings");
 | 
			
		||||
        this.$store.dispatch("loadUser");
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <a v-if="visible" href="authentication/profile" class="btn">
 | 
			
		||||
    <a v-if="visible" href="Identity/Account/Manage" class="btn">
 | 
			
		||||
        <slot v-if="username" :displayName="username" name="username">
 | 
			
		||||
            {{ username }}
 | 
			
		||||
        </slot>
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import WaitCircle from "@/components/WaitCircle.vue";
 | 
			
		||||
import WaitCircle from "./WaitCircle.vue";
 | 
			
		||||
export default {
 | 
			
		||||
    components: {
 | 
			
		||||
        WaitCircle
 | 
			
		||||
@@ -32,18 +32,18 @@ export default {
 | 
			
		||||
    computed: {
 | 
			
		||||
        username() {
 | 
			
		||||
            return this.$store.getters.username &&
 | 
			
		||||
                !this.$store.state.identity.loading
 | 
			
		||||
                !this.$store.getters.isIdentityLoading
 | 
			
		||||
                ? this.$store.getters.username
 | 
			
		||||
                : null;
 | 
			
		||||
        },
 | 
			
		||||
        visible() {
 | 
			
		||||
            return !this.showUnauthenticated
 | 
			
		||||
                ? this.$store.getters.isAuthenticated ||
 | 
			
		||||
                      this.$store.state.identity.loading
 | 
			
		||||
                      this.$store.getters.isIdentityLoading
 | 
			
		||||
                : true;
 | 
			
		||||
        },
 | 
			
		||||
        isProfileLoading() {
 | 
			
		||||
            return this.$store.state.identity.loading;
 | 
			
		||||
            return this.$store.getters.isIdentityLoading;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    name: "ProfileLogin",
 | 
			
		||||
    name: "ProfileLogIn",
 | 
			
		||||
    computed: {
 | 
			
		||||
        visible() {
 | 
			
		||||
            return !this.$store.getters.isAuthenticated && !this.$store.state.identity.loading;
 | 
			
		||||
            return !this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ export default {
 | 
			
		||||
    name: "ProfileLogOut",
 | 
			
		||||
    computed: {
 | 
			
		||||
        visible() {
 | 
			
		||||
            return this.$store.getters.isAuthenticated && !this.$store.state.identity.loading;
 | 
			
		||||
            return this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								MultiShop/client/src/components/ProfileSignUp.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								MultiShop/client/src/components/ProfileSignUp.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <button v-if="visible" @click="onClick" type="button" class="btn">
 | 
			
		||||
        <slot>Sign Up!</slot>
 | 
			
		||||
    </button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
    name: "ProfileSignUp",
 | 
			
		||||
    computed: {
 | 
			
		||||
        visible() {
 | 
			
		||||
            return this.$store.state.identity.registrationEnabled && !this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        onClick() {
 | 
			
		||||
            this.$store.dispatch("beginRegistration");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -10,4 +10,13 @@ const userManager = new UserManager({
 | 
			
		||||
    userStore: new WebStorageStateStore({ store: window.localStorage }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export { userManager };
 | 
			
		||||
const identityPaths = {
 | 
			
		||||
    Manage: "Identity/Account/Manage",
 | 
			
		||||
    Register: "/Identity/Account/Register"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const identityQueryParameters = {
 | 
			
		||||
    ReturnUrl: "returnUrl",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { userManager, identityPaths, identityQueryParameters };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,28 @@
 | 
			
		||||
import router from "../router";
 | 
			
		||||
import { userManager } from "../services/authentication";
 | 
			
		||||
import { addBearerTokenInterceptor, removeBearerTokenInterceptor } from "../services/http";
 | 
			
		||||
import { identityPaths, identityQueryParameters, userManager } from "../services/authentication";
 | 
			
		||||
import { addBearerTokenInterceptor, http, removeBearerTokenInterceptor } from "../services/http";
 | 
			
		||||
import { get, put } from "../services/persistence";
 | 
			
		||||
 | 
			
		||||
const identity = {
 | 
			
		||||
    state: () => ({
 | 
			
		||||
        user: null,
 | 
			
		||||
        loading: true,
 | 
			
		||||
        loadingLayers: 0,
 | 
			
		||||
        callbackPath: null,
 | 
			
		||||
        registrationEnabled: false,
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {
 | 
			
		||||
        isAuthenticated(state) {
 | 
			
		||||
            return state.user ? !state.user.expired : false;
 | 
			
		||||
        },
 | 
			
		||||
        isIdentityLoading(state) {
 | 
			
		||||
            return state.loadingLayers > 0;
 | 
			
		||||
        },
 | 
			
		||||
        username(state) {
 | 
			
		||||
            return (state.user && state.user.profile.name) ? state.user.profile.name : null;
 | 
			
		||||
        },
 | 
			
		||||
        authCallbackLocation(state) {
 | 
			
		||||
            return state.callbackPath ? state.callbackPath : get("callbackPath");
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mutations: {
 | 
			
		||||
        login(state, { user }) {
 | 
			
		||||
@@ -31,14 +35,20 @@ const identity = {
 | 
			
		||||
            removeBearerTokenInterceptor();
 | 
			
		||||
        },
 | 
			
		||||
        beginAuthenticating(state) {
 | 
			
		||||
            state.loading = true;
 | 
			
		||||
            state.loading += 1;
 | 
			
		||||
        },
 | 
			
		||||
        endAuthenticating(state) {
 | 
			
		||||
            state.loading = false;
 | 
			
		||||
            state.loading -= 1;
 | 
			
		||||
        },
 | 
			
		||||
        authCallbackLocation(state) {
 | 
			
		||||
            state.callbackPath = window.location.pathname;
 | 
			
		||||
            put("callbackPath", state.callbackPath);
 | 
			
		||||
        },
 | 
			
		||||
        enableIdentificationRegistration(state) {
 | 
			
		||||
            state.registrationEnabled = true;
 | 
			
		||||
        },
 | 
			
		||||
        disableIdentificationRegistration(state) {
 | 
			
		||||
            state.registrationEnabled = false;
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    actions: {
 | 
			
		||||
@@ -50,20 +60,24 @@ const identity = {
 | 
			
		||||
            }
 | 
			
		||||
            context.commit("endAuthenticating");
 | 
			
		||||
        },
 | 
			
		||||
        async attemptSilentAuthentication(context) {
 | 
			
		||||
        async attemptSilentAuthentication(context, options) {
 | 
			
		||||
            if (context.getters.isAuthenticated) return;
 | 
			
		||||
            context.commit("beginAuthenticating");
 | 
			
		||||
            context.dispatch("loadUser");
 | 
			
		||||
            if (!context.getters.isAuthenticated) {
 | 
			
		||||
                try {
 | 
			
		||||
                    const user = await userManager.signinSilent({
 | 
			
		||||
                        redirect_uri: window.location.origin + "/authentication/silent-login-callback"
 | 
			
		||||
                        redirect_uri: window.location.origin + "/authentication/silent-login-callback.html"
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    context.commit("login", { user });
 | 
			
		||||
                } catch { }
 | 
			
		||||
                context.commit("endAuthenticating");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (options && options.redirect) {
 | 
			
		||||
                router.replace(context.getters.authCallbackLocation);
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        async beginAuthentication(context) {
 | 
			
		||||
            if (context.getters.isAuthenticated) return;
 | 
			
		||||
@@ -78,20 +92,39 @@ const identity = {
 | 
			
		||||
            if (!context.getters.isAuthenticated) return;
 | 
			
		||||
            context.commit("beginAuthenticating");
 | 
			
		||||
            context.commit("authCallbackLocation");
 | 
			
		||||
            await userManager.removeUser();
 | 
			
		||||
            userManager.signoutRedirect();
 | 
			
		||||
        },
 | 
			
		||||
        async completeAuthentication(context, { user }) {
 | 
			
		||||
            if (!user) return;
 | 
			
		||||
            context.commit("login", { user });
 | 
			
		||||
            router.push(context.getters.authCallbackLocation);
 | 
			
		||||
            context.commit("endAuthenticating");
 | 
			
		||||
            router.replace(context.getters.authCallbackLocation);
 | 
			
		||||
        },
 | 
			
		||||
        async completeDeauthentication(context) {
 | 
			
		||||
            if (!context.getters.isAuthenticated) return;
 | 
			
		||||
            context.commit("logout");
 | 
			
		||||
            router.push(context.getters.authCallbackLocation);
 | 
			
		||||
            try {
 | 
			
		||||
                await userManager.removeUser();
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error(error);
 | 
			
		||||
            }
 | 
			
		||||
            context.commit("endAuthenticating");
 | 
			
		||||
            router.replace(context.getters.authCallbackLocation);
 | 
			
		||||
        },
 | 
			
		||||
        beginRegistration(context) {
 | 
			
		||||
            context.commit("authCallbackLocation");
 | 
			
		||||
            window.location.replace(window.location.origin + identityPaths.Register + "?" + identityQueryParameters.ReturnUrl + "=" + "/authentication/silent-login");
 | 
			
		||||
        },
 | 
			
		||||
        async updatePublicApiSettings(context) {
 | 
			
		||||
            try {
 | 
			
		||||
                const settings = (await http.get("PublicApiSettings")).data;
 | 
			
		||||
                if (settings.RegistrationEnabled === "True") {
 | 
			
		||||
                    context.commit("enableIdentificationRegistration");
 | 
			
		||||
                } else {
 | 
			
		||||
                    context.commit("disableIdentificationRegistration");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                console.error(error);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import WaitCircle from "@/components/WaitCircle.vue";
 | 
			
		||||
import WaitCircle from "../components/WaitCircle.vue";
 | 
			
		||||
import { userManager } from "../services/authentication";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -36,14 +36,15 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
        async completeCallback() {
 | 
			
		||||
            console.log("Completing callback for " + this.action);
 | 
			
		||||
            if (this.action === "login-callback") {
 | 
			
		||||
                const user = await userManager.signinRedirectCallback();
 | 
			
		||||
                this.$store.dispatch("completeAuthentication", { user });
 | 
			
		||||
            } else if (this.action === "silent-login-callback") {
 | 
			
		||||
                await userManager.signinSilentCallback();
 | 
			
		||||
            } else if (this.action === "logout-callback") {
 | 
			
		||||
                await userManager.signoutRedirectCallback();
 | 
			
		||||
                this.$store.dispatch("completeDeauthentication");
 | 
			
		||||
            } else if (this.action === "silent-login") {
 | 
			
		||||
                this.$store.dispatch("attemptSilentAuthentication", { redirect: true });
 | 
			
		||||
            } else {
 | 
			
		||||
                console.warn("Unknown callback: " + this.action);
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -37,9 +37,9 @@ namespace MultiShop
 | 
			
		||||
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
 | 
			
		||||
                    options => {
 | 
			
		||||
                        options.Clients.AddIdentityServerSPA("MultiShop", spa => {
 | 
			
		||||
                            spa.WithRedirectUri("/authentication/silent-login-callback");
 | 
			
		||||
                            spa.WithRedirectUri("/authentication/silent-login-callback.html");
 | 
			
		||||
                            spa.WithRedirectUri("/authentication/login-callback");
 | 
			
		||||
                            spa.WithRedirectUri("/authentication/logout-callback");
 | 
			
		||||
                            spa.WithLogoutRedirectUri("/authentication/logout-callback");
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user