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>
|
||||||
</ProfileDisplay>
|
</ProfileDisplay>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<ProfileSignUp>
|
||||||
|
</ProfileSignUp>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<ProfileLogIn>
|
<ProfileLogIn>
|
||||||
</ProfileLogIn>
|
</ProfileLogIn>
|
||||||
@ -57,14 +61,17 @@ import "./assets/scss/main.scss";
|
|||||||
import ProfileDisplay from "./components/ProfileDisplay.vue";
|
import ProfileDisplay from "./components/ProfileDisplay.vue";
|
||||||
import ProfileLogIn from "./components/ProfileLogIn.vue";
|
import ProfileLogIn from "./components/ProfileLogIn.vue";
|
||||||
import ProfileLogOut from "./components/ProfileLogOut.vue";
|
import ProfileLogOut from "./components/ProfileLogOut.vue";
|
||||||
|
import ProfileSignUp from "./components/ProfileSignUp.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ProfileDisplay,
|
ProfileDisplay,
|
||||||
ProfileLogIn,
|
ProfileLogIn,
|
||||||
|
ProfileSignUp,
|
||||||
ProfileLogOut
|
ProfileLogOut
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch("attemptSilentAuthentication");
|
this.$store.dispatch("updatePublicApiSettings");
|
||||||
|
this.$store.dispatch("loadUser");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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">
|
<slot v-if="username" :displayName="username" name="username">
|
||||||
{{ username }}
|
{{ username }}
|
||||||
</slot>
|
</slot>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import WaitCircle from "@/components/WaitCircle.vue";
|
import WaitCircle from "./WaitCircle.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
WaitCircle
|
WaitCircle
|
||||||
@ -32,18 +32,18 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
username() {
|
username() {
|
||||||
return this.$store.getters.username &&
|
return this.$store.getters.username &&
|
||||||
!this.$store.state.identity.loading
|
!this.$store.getters.isIdentityLoading
|
||||||
? this.$store.getters.username
|
? this.$store.getters.username
|
||||||
: null;
|
: null;
|
||||||
},
|
},
|
||||||
visible() {
|
visible() {
|
||||||
return !this.showUnauthenticated
|
return !this.showUnauthenticated
|
||||||
? this.$store.getters.isAuthenticated ||
|
? this.$store.getters.isAuthenticated ||
|
||||||
this.$store.state.identity.loading
|
this.$store.getters.isIdentityLoading
|
||||||
: true;
|
: true;
|
||||||
},
|
},
|
||||||
isProfileLoading() {
|
isProfileLoading() {
|
||||||
return this.$store.state.identity.loading;
|
return this.$store.getters.isIdentityLoading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "ProfileLogin",
|
name: "ProfileLogIn",
|
||||||
computed: {
|
computed: {
|
||||||
visible() {
|
visible() {
|
||||||
return !this.$store.getters.isAuthenticated && !this.$store.state.identity.loading;
|
return !this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -9,7 +9,7 @@ export default {
|
|||||||
name: "ProfileLogOut",
|
name: "ProfileLogOut",
|
||||||
computed: {
|
computed: {
|
||||||
visible() {
|
visible() {
|
||||||
return this.$store.getters.isAuthenticated && !this.$store.state.identity.loading;
|
return this.$store.getters.isAuthenticated && !this.$store.getters.isIdentityLoading;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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 }),
|
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 router from "../router";
|
||||||
import { userManager } from "../services/authentication";
|
import { identityPaths, identityQueryParameters, userManager } from "../services/authentication";
|
||||||
import { addBearerTokenInterceptor, removeBearerTokenInterceptor } from "../services/http";
|
import { addBearerTokenInterceptor, http, removeBearerTokenInterceptor } from "../services/http";
|
||||||
import { get, put } from "../services/persistence";
|
import { get, put } from "../services/persistence";
|
||||||
|
|
||||||
const identity = {
|
const identity = {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
user: null,
|
user: null,
|
||||||
loading: true,
|
loadingLayers: 0,
|
||||||
callbackPath: null,
|
callbackPath: null,
|
||||||
|
registrationEnabled: false,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated(state) {
|
isAuthenticated(state) {
|
||||||
return state.user ? !state.user.expired : false;
|
return state.user ? !state.user.expired : false;
|
||||||
},
|
},
|
||||||
|
isIdentityLoading(state) {
|
||||||
|
return state.loadingLayers > 0;
|
||||||
|
},
|
||||||
username(state) {
|
username(state) {
|
||||||
return (state.user && state.user.profile.name) ? state.user.profile.name : null;
|
return (state.user && state.user.profile.name) ? state.user.profile.name : null;
|
||||||
},
|
},
|
||||||
authCallbackLocation(state) {
|
authCallbackLocation(state) {
|
||||||
return state.callbackPath ? state.callbackPath : get("callbackPath");
|
return state.callbackPath ? state.callbackPath : get("callbackPath");
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
login(state, { user }) {
|
login(state, { user }) {
|
||||||
@ -31,14 +35,20 @@ const identity = {
|
|||||||
removeBearerTokenInterceptor();
|
removeBearerTokenInterceptor();
|
||||||
},
|
},
|
||||||
beginAuthenticating(state) {
|
beginAuthenticating(state) {
|
||||||
state.loading = true;
|
state.loading += 1;
|
||||||
},
|
},
|
||||||
endAuthenticating(state) {
|
endAuthenticating(state) {
|
||||||
state.loading = false;
|
state.loading -= 1;
|
||||||
},
|
},
|
||||||
authCallbackLocation(state) {
|
authCallbackLocation(state) {
|
||||||
state.callbackPath = window.location.pathname;
|
state.callbackPath = window.location.pathname;
|
||||||
put("callbackPath", state.callbackPath);
|
put("callbackPath", state.callbackPath);
|
||||||
|
},
|
||||||
|
enableIdentificationRegistration(state) {
|
||||||
|
state.registrationEnabled = true;
|
||||||
|
},
|
||||||
|
disableIdentificationRegistration(state) {
|
||||||
|
state.registrationEnabled = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -50,20 +60,24 @@ const identity = {
|
|||||||
}
|
}
|
||||||
context.commit("endAuthenticating");
|
context.commit("endAuthenticating");
|
||||||
},
|
},
|
||||||
async attemptSilentAuthentication(context) {
|
async attemptSilentAuthentication(context, options) {
|
||||||
if (context.getters.isAuthenticated) return;
|
if (context.getters.isAuthenticated) return;
|
||||||
context.commit("beginAuthenticating");
|
context.commit("beginAuthenticating");
|
||||||
context.dispatch("loadUser");
|
context.dispatch("loadUser");
|
||||||
if (!context.getters.isAuthenticated) {
|
if (!context.getters.isAuthenticated) {
|
||||||
try {
|
try {
|
||||||
const user = await userManager.signinSilent({
|
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 });
|
context.commit("login", { user });
|
||||||
} catch { }
|
} catch { }
|
||||||
context.commit("endAuthenticating");
|
context.commit("endAuthenticating");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.redirect) {
|
||||||
|
router.replace(context.getters.authCallbackLocation);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async beginAuthentication(context) {
|
async beginAuthentication(context) {
|
||||||
if (context.getters.isAuthenticated) return;
|
if (context.getters.isAuthenticated) return;
|
||||||
@ -78,20 +92,39 @@ const identity = {
|
|||||||
if (!context.getters.isAuthenticated) return;
|
if (!context.getters.isAuthenticated) return;
|
||||||
context.commit("beginAuthenticating");
|
context.commit("beginAuthenticating");
|
||||||
context.commit("authCallbackLocation");
|
context.commit("authCallbackLocation");
|
||||||
await userManager.removeUser();
|
|
||||||
userManager.signoutRedirect();
|
userManager.signoutRedirect();
|
||||||
},
|
},
|
||||||
async completeAuthentication(context, { user }) {
|
async completeAuthentication(context, { user }) {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
context.commit("login", { user });
|
context.commit("login", { user });
|
||||||
router.push(context.getters.authCallbackLocation);
|
|
||||||
context.commit("endAuthenticating");
|
context.commit("endAuthenticating");
|
||||||
|
router.replace(context.getters.authCallbackLocation);
|
||||||
},
|
},
|
||||||
async completeDeauthentication(context) {
|
async completeDeauthentication(context) {
|
||||||
if (!context.getters.isAuthenticated) return;
|
|
||||||
context.commit("logout");
|
context.commit("logout");
|
||||||
router.push(context.getters.authCallbackLocation);
|
try {
|
||||||
|
await userManager.removeUser();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
context.commit("endAuthenticating");
|
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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import WaitCircle from "@/components/WaitCircle.vue";
|
import WaitCircle from "../components/WaitCircle.vue";
|
||||||
import { userManager } from "../services/authentication";
|
import { userManager } from "../services/authentication";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -36,14 +36,15 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async completeCallback() {
|
async completeCallback() {
|
||||||
|
console.log("Completing callback for " + this.action);
|
||||||
if (this.action === "login-callback") {
|
if (this.action === "login-callback") {
|
||||||
const user = await userManager.signinRedirectCallback();
|
const user = await userManager.signinRedirectCallback();
|
||||||
this.$store.dispatch("completeAuthentication", { user });
|
this.$store.dispatch("completeAuthentication", { user });
|
||||||
} else if (this.action === "silent-login-callback") {
|
|
||||||
await userManager.signinSilentCallback();
|
|
||||||
} else if (this.action === "logout-callback") {
|
} else if (this.action === "logout-callback") {
|
||||||
await userManager.signoutRedirectCallback();
|
await userManager.signoutRedirectCallback();
|
||||||
this.$store.dispatch("completeDeauthentication");
|
this.$store.dispatch("completeDeauthentication");
|
||||||
|
} else if (this.action === "silent-login") {
|
||||||
|
this.$store.dispatch("attemptSilentAuthentication", { redirect: true });
|
||||||
} else {
|
} else {
|
||||||
console.warn("Unknown callback: " + this.action);
|
console.warn("Unknown callback: " + this.action);
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,9 @@ namespace MultiShop
|
|||||||
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
|
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(
|
||||||
options => {
|
options => {
|
||||||
options.Clients.AddIdentityServerSPA("MultiShop", spa => {
|
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/login-callback");
|
||||||
spa.WithRedirectUri("/authentication/logout-callback");
|
spa.WithLogoutRedirectUri("/authentication/logout-callback");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user