Added sign up button and improved logout flow.
Also added proper link to profile management.
This commit is contained in:
parent
9e55b459fc
commit
c597d65256
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");
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user