@moreillon/vue-oidc 0.0.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -41
- package/dist/composables/useAuth.d.ts +27 -87
- package/dist/composables/useAuth.js +30 -16
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -2
- package/dist/plugins/auth.d.ts +3 -8
- package/dist/plugins/auth.js +38 -16
- package/package.json +31 -28
package/README.md
CHANGED
|
@@ -1,41 +1,87 @@
|
|
|
1
|
-
# Vue OIDC
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
# Vue OIDC
|
|
2
|
+
|
|
3
|
+
Vue 3 plugin and composable for OpenID Connect authentication, built on [@moreillon/oidc-auth](https://www.npmjs.com/package/@moreillon/oidc-auth).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @moreillon/vue-oidc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`vue` (^3.5) is a peer dependency.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createApp } from "vue";
|
|
17
|
+
import { auth } from "@moreillon/vue-oidc";
|
|
18
|
+
import App from "./App.vue";
|
|
19
|
+
|
|
20
|
+
const { VITE_OIDC_AUTHORITY, VITE_OIDC_CLIENT_ID } = import.meta.env;
|
|
21
|
+
|
|
22
|
+
createApp(App)
|
|
23
|
+
.use(auth, {
|
|
24
|
+
authority: VITE_OIDC_AUTHORITY,
|
|
25
|
+
client_id: VITE_OIDC_CLIENT_ID,
|
|
26
|
+
})
|
|
27
|
+
.mount("#app");
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Options
|
|
31
|
+
|
|
32
|
+
| Option | Type | Description |
|
|
33
|
+
| ------------------ | ------------------------ | -------------------------------------------------------------------------------- |
|
|
34
|
+
| `authority` | `string` | URL of the OIDC provider (required) |
|
|
35
|
+
| `client_id` | `string` | OIDC client ID (required) |
|
|
36
|
+
| `redirect_uri` | `string` | Where the provider redirects after login. Defaults to the current page |
|
|
37
|
+
| `extraQueryParams` | `Record<string, string>` | Additional query parameters sent to the authorization endpoint |
|
|
38
|
+
| `enforce` | `boolean` | Redirect unauthenticated users to the provider immediately instead of letting them in anonymously |
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
Initialization is asynchronous and the app mounts before it finishes, so wait for `ready` before rendering anything that depends on authentication:
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<script setup lang="ts">
|
|
46
|
+
import { useAuth } from "@moreillon/vue-oidc";
|
|
47
|
+
|
|
48
|
+
const { ready, error, user, login, logout } = useAuth();
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<p v-if="!ready">Loading...</p>
|
|
53
|
+
<p v-else-if="error">Authentication failed: {{ error }}</p>
|
|
54
|
+
<template v-else-if="user">
|
|
55
|
+
<p>Hello {{ user.name }}</p>
|
|
56
|
+
<button @click="logout">Log out</button>
|
|
57
|
+
</template>
|
|
58
|
+
<button v-else @click="login">Log in</button>
|
|
59
|
+
</template>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `useAuth()`
|
|
63
|
+
|
|
64
|
+
All refs are read-only.
|
|
65
|
+
|
|
66
|
+
| Property | Type | Description |
|
|
67
|
+
| -------------- | --------------------------------- | --------------------------------------------------------------------------- |
|
|
68
|
+
| `ready` | `Ref<boolean>` | `true` once initialization has finished (successfully or not) |
|
|
69
|
+
| `error` | `Ref<unknown>` | Error during initialization (e.g. provider unreachable, login cancelled) or when the session could not be renewed. Cleared on the next successful refresh |
|
|
70
|
+
| `user` | `Ref<User \| null>` | User info of the authenticated user. Cleared when the user logs out in another tab or the provider rejects the session |
|
|
71
|
+
| `access_token` | `Ref<string \| null>` | Current access token, kept up to date on refresh |
|
|
72
|
+
| `client` | `Ref<OidcClient \| null>` | Underlying `@moreillon/oidc-auth` client |
|
|
73
|
+
| `login()` | `() => void` | Redirect to the provider's login page |
|
|
74
|
+
| `logout()` | `() => void` | Log out at the provider |
|
|
75
|
+
|
|
76
|
+
`error.value instanceof TokenRejectedError` (exported by this package) tells a session rejected by the provider apart from network or server errors.
|
|
77
|
+
|
|
78
|
+
`useAuth` accepts a type parameter for custom claims: `useAuth<MyUser>()`.
|
|
79
|
+
|
|
80
|
+
The state is shared at module level, so `useAuth()` also works outside components, e.g. in router guards or API clients:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const { access_token } = useAuth();
|
|
84
|
+
fetch("/api/items", {
|
|
85
|
+
headers: { Authorization: `Bearer ${access_token.value}` },
|
|
86
|
+
});
|
|
87
|
+
```
|
|
@@ -1,93 +1,33 @@
|
|
|
1
1
|
import type OidcClient from "@moreillon/oidc-auth";
|
|
2
|
-
|
|
3
|
-
declare
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
authorization_endpoint: string;
|
|
18
|
-
token_endpoint: string;
|
|
19
|
-
userinfo_endpoint: string;
|
|
20
|
-
end_session_endpoint: string;
|
|
21
|
-
} | undefined;
|
|
22
|
-
refreshEventHandlers: Function[];
|
|
23
|
-
init: (enforce?: boolean) => Promise<import("@moreillon/oidc-auth").InitOutput | undefined>;
|
|
24
|
-
sendUserToAuthUrl: (saveHref: Boolean) => Promise<void>;
|
|
25
|
-
fetchOidcConfig: () => Promise<any>;
|
|
26
|
-
generateAuthUrl: () => Promise<string>;
|
|
27
|
-
createTimeoutForTokenExpiry: (oidConfig: import("@moreillon/oidc-auth").OidcConfig) => void;
|
|
28
|
-
makeCookieOptions: () => string;
|
|
29
|
-
makeExpiryDate: (expires_in: number) => Date;
|
|
30
|
-
isExpired: (expires_at: string) => boolean;
|
|
31
|
-
saveAuthDataInCookies: (data: {
|
|
32
|
-
expires_in: number;
|
|
33
|
-
}) => {
|
|
34
|
-
expires_at: string;
|
|
35
|
-
expires_in: number;
|
|
36
|
-
};
|
|
37
|
-
exchangeCodeForToken: (code: string) => Promise<void>;
|
|
38
|
-
getUser: () => Promise<any>;
|
|
39
|
-
refreshAccessToken: (oidcConfig: import("@moreillon/oidc-auth").OidcConfig) => Promise<{
|
|
40
|
-
expires_at: string;
|
|
41
|
-
expires_in: number;
|
|
42
|
-
}>;
|
|
43
|
-
onTokenRefreshed: (handler: (oidcData: import("@moreillon/oidc-auth").Tokens) => void) => void;
|
|
44
|
-
runRefreshEventHandlers: () => void;
|
|
45
|
-
login: () => void;
|
|
46
|
-
logout: () => void;
|
|
47
|
-
} | null, OidcClient | {
|
|
48
|
-
cookieName: string;
|
|
49
|
-
options: {
|
|
50
|
-
redirect_uri: string;
|
|
51
|
-
authority: string;
|
|
52
|
-
client_id: string;
|
|
53
|
-
extraQueryParams?: {
|
|
54
|
-
[key: string]: string;
|
|
55
|
-
} | undefined;
|
|
56
|
-
};
|
|
57
|
-
openidConfig?: {
|
|
58
|
-
authorization_endpoint: string;
|
|
59
|
-
token_endpoint: string;
|
|
60
|
-
userinfo_endpoint: string;
|
|
61
|
-
end_session_endpoint: string;
|
|
62
|
-
} | undefined;
|
|
63
|
-
refreshEventHandlers: Function[];
|
|
64
|
-
init: (enforce?: boolean) => Promise<import("@moreillon/oidc-auth").InitOutput | undefined>;
|
|
65
|
-
sendUserToAuthUrl: (saveHref: Boolean) => Promise<void>;
|
|
66
|
-
fetchOidcConfig: () => Promise<any>;
|
|
67
|
-
generateAuthUrl: () => Promise<string>;
|
|
68
|
-
createTimeoutForTokenExpiry: (oidConfig: import("@moreillon/oidc-auth").OidcConfig) => void;
|
|
69
|
-
makeCookieOptions: () => string;
|
|
70
|
-
makeExpiryDate: (expires_in: number) => Date;
|
|
71
|
-
isExpired: (expires_at: string) => boolean;
|
|
72
|
-
saveAuthDataInCookies: (data: {
|
|
73
|
-
expires_in: number;
|
|
74
|
-
}) => {
|
|
75
|
-
expires_at: string;
|
|
76
|
-
expires_in: number;
|
|
77
|
-
};
|
|
78
|
-
exchangeCodeForToken: (code: string) => Promise<void>;
|
|
79
|
-
getUser: () => Promise<any>;
|
|
80
|
-
refreshAccessToken: (oidcConfig: import("@moreillon/oidc-auth").OidcConfig) => Promise<{
|
|
81
|
-
expires_at: string;
|
|
82
|
-
expires_in: number;
|
|
83
|
-
}>;
|
|
84
|
-
onTokenRefreshed: (handler: (oidcData: import("@moreillon/oidc-auth").Tokens) => void) => void;
|
|
85
|
-
runRefreshEventHandlers: () => void;
|
|
86
|
-
login: () => void;
|
|
87
|
-
logout: () => void;
|
|
2
|
+
import type { User } from "@moreillon/oidc-auth";
|
|
3
|
+
export declare const state: {
|
|
4
|
+
client: import("vue").ShallowRef<OidcClient | null, OidcClient | null>;
|
|
5
|
+
user: import("vue").Ref<{
|
|
6
|
+
[x: string]: unknown;
|
|
7
|
+
name: string;
|
|
8
|
+
email: string;
|
|
9
|
+
family_name: string;
|
|
10
|
+
given_name: string;
|
|
11
|
+
} | null, User | {
|
|
12
|
+
[x: string]: unknown;
|
|
13
|
+
name: string;
|
|
14
|
+
email: string;
|
|
15
|
+
family_name: string;
|
|
16
|
+
given_name: string;
|
|
88
17
|
} | null>;
|
|
89
|
-
user: import("vue").Ref<any, any>;
|
|
90
18
|
access_token: import("vue").Ref<string | null, string | null>;
|
|
19
|
+
ready: import("vue").Ref<boolean, boolean>;
|
|
20
|
+
error: import("vue").ShallowRef<unknown, unknown>;
|
|
21
|
+
};
|
|
22
|
+
export declare function resetState(): void;
|
|
23
|
+
declare function login(): void;
|
|
24
|
+
declare function logout(): void;
|
|
25
|
+
export default function useAuth<T extends User = User>(): {
|
|
26
|
+
ready: Readonly<import("vue").Ref<boolean, boolean>>;
|
|
27
|
+
error: import("vue").ComputedRef<unknown>;
|
|
28
|
+
client: import("vue").ComputedRef<OidcClient | null>;
|
|
29
|
+
user: import("vue").ComputedRef<T | null>;
|
|
30
|
+
access_token: Readonly<import("vue").Ref<string | null, string | null>>;
|
|
91
31
|
login: typeof login;
|
|
92
32
|
logout: typeof logout;
|
|
93
33
|
};
|
|
@@ -1,24 +1,38 @@
|
|
|
1
|
-
import { ref } from "vue";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
1
|
+
import { computed, readonly, ref, shallowRef } from "vue";
|
|
2
|
+
// Module-level state so that useAuth() also works outside of components
|
|
3
|
+
// (e.g. in router navigation guards). Only the plugin may write to it.
|
|
4
|
+
export const state = {
|
|
5
|
+
client: shallowRef(null),
|
|
6
|
+
user: ref(null),
|
|
7
|
+
access_token: ref(null),
|
|
8
|
+
ready: ref(false),
|
|
9
|
+
error: shallowRef(null),
|
|
10
|
+
};
|
|
11
|
+
export function resetState() {
|
|
12
|
+
state.client.value = null;
|
|
13
|
+
state.user.value = null;
|
|
14
|
+
state.access_token.value = null;
|
|
15
|
+
state.ready.value = false;
|
|
16
|
+
state.error.value = null;
|
|
17
|
+
}
|
|
18
|
+
function getClient() {
|
|
19
|
+
if (!state.client.value)
|
|
20
|
+
throw new Error("OIDC Client not instantiated");
|
|
21
|
+
return state.client.value;
|
|
22
|
+
}
|
|
6
23
|
function login() {
|
|
7
|
-
|
|
8
|
-
throw new Error("OIDC Client not instanciated");
|
|
9
|
-
client.value.login();
|
|
24
|
+
getClient().login();
|
|
10
25
|
}
|
|
11
26
|
function logout() {
|
|
12
|
-
|
|
13
|
-
throw new Error("OIDC Client not instanciated");
|
|
14
|
-
client.value.logout();
|
|
27
|
+
getClient().logout();
|
|
15
28
|
}
|
|
16
|
-
export default function () {
|
|
29
|
+
export default function useAuth() {
|
|
17
30
|
return {
|
|
18
|
-
ready,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
ready: readonly(state.ready),
|
|
32
|
+
error: computed(() => state.error.value),
|
|
33
|
+
client: computed(() => state.client.value),
|
|
34
|
+
user: computed(() => state.user.value),
|
|
35
|
+
access_token: readonly(state.access_token),
|
|
22
36
|
login,
|
|
23
37
|
logout,
|
|
24
38
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import auth from "./plugins/auth";
|
|
2
|
-
import useAuth from "./composables/useAuth";
|
|
1
|
+
import auth from "./plugins/auth.js";
|
|
2
|
+
import useAuth from "./composables/useAuth.js";
|
|
3
3
|
export { auth, useAuth };
|
|
4
|
+
export type { Options } from "./plugins/auth.js";
|
|
5
|
+
export { TokenRejectedError } from "@moreillon/oidc-auth";
|
|
6
|
+
export type { User, Tokens } from "@moreillon/oidc-auth";
|
package/dist/index.js
CHANGED
package/dist/plugins/auth.d.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import type { App } from "vue";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
client_id: string;
|
|
5
|
-
extraQueryParams?: {
|
|
6
|
-
[key: string]: string;
|
|
7
|
-
};
|
|
8
|
-
redirect_uri?: string;
|
|
2
|
+
import type { UserProvidedOptions } from "@moreillon/oidc-auth";
|
|
3
|
+
export type Options = UserProvidedOptions & {
|
|
9
4
|
enforce?: boolean;
|
|
10
5
|
};
|
|
11
6
|
declare const _default: {
|
|
12
|
-
install(
|
|
7
|
+
install(_app: App, options: Options): Promise<void>;
|
|
13
8
|
};
|
|
14
9
|
export default _default;
|
package/dist/plugins/auth.js
CHANGED
|
@@ -1,20 +1,42 @@
|
|
|
1
|
-
import useAuth from "../composables/useAuth";
|
|
2
1
|
import OidcClient from "@moreillon/oidc-auth";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
2
|
+
import { resetState, state } from "../composables/useAuth.js";
|
|
3
|
+
async function init(options) {
|
|
4
|
+
const { enforce, ...oidcOptions } = options;
|
|
5
|
+
resetState();
|
|
6
|
+
try {
|
|
7
|
+
const client = new OidcClient(oidcOptions);
|
|
8
|
+
state.client.value = client;
|
|
9
|
+
const oidcData = await client.init(enforce);
|
|
10
|
+
if (oidcData) {
|
|
11
|
+
state.access_token.value = oidcData.tokens.access_token;
|
|
12
|
+
state.user.value = oidcData.user;
|
|
13
|
+
// Also fired when another tab refreshed the tokens
|
|
14
|
+
client.onTokenRefreshed(({ access_token }) => {
|
|
15
|
+
state.access_token.value = access_token;
|
|
16
|
+
state.error.value = null;
|
|
17
|
+
});
|
|
18
|
+
client.onRefreshError((error) => {
|
|
19
|
+
state.error.value = error;
|
|
20
|
+
});
|
|
21
|
+
// Logged out in another tab, or tokens rejected by the provider
|
|
22
|
+
client.onLogout(() => {
|
|
23
|
+
state.user.value = null;
|
|
24
|
+
state.access_token.value = null;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// When enforced without data, the user is being redirected to the provider
|
|
9
28
|
if (!enforce || oidcData)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
state.ready.value = true;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
state.error.value = error;
|
|
33
|
+
state.ready.value = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export default {
|
|
37
|
+
// Vue does not await plugin installation: returned promise is for tests / callers
|
|
38
|
+
// who want to wait. Apps should rely on the `ready` ref from useAuth().
|
|
39
|
+
install(_app, options) {
|
|
40
|
+
return init(options);
|
|
19
41
|
},
|
|
20
42
|
};
|
package/package.json
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@moreillon/vue-oidc",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"type": "module",
|
|
5
|
-
"files": [
|
|
6
|
-
"dist"
|
|
7
|
-
],
|
|
8
|
-
"main": "dist/index.js",
|
|
9
|
-
"types": "dist/index.d.ts",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@moreillon/vue-oidc",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "vitest run"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"vue": "^3.5.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@moreillon/oidc-auth": "^4.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"typescript": "~5.8.0",
|
|
28
|
+
"vitest": "^4.1.12",
|
|
29
|
+
"vue": "^3.5.44"
|
|
30
|
+
}
|
|
31
|
+
}
|