Dan6erbond / sk-auth

Authentication library for use with SvelteKit featuring built-in OAuth providers and zero restriction customization!
MIT License
577 stars 70 forks source link

Getting Started #7

Closed SGarno closed 3 years ago

SGarno commented 3 years ago

Love what you have going here! I was contemplating writing something along these lines since I am trying to use SK with an external API that conditionally uses OAuth2 (based on an initial request-response). Because of the SSR nature of SK and using hooks.js doesn't seem like the right way to do things for external APIs.

I realize you are still getting this up and running, but do you have a sample svelte.config.js for this?

I am getting issues with @sveltejs/adapter-static and vite complaining that it cannot resolve the package.

Here is what I have:

/** @type {import('@sveltejs/kit').Config} */

import adapter from "@sveltejs/adapter-static";
import * as carbon from "carbon-preprocess-svelte";

const config = {
    preprocess: carbon.presetCarbon(),
    kit: {
        target: '#svelte',
        adapter: adapter(),
        vite: {
            optimizeDeps: { include: ["clipboard-copy"] },
            plugins: [process.env.NODE_ENV === "production" && carbon.optimizeCss()],
            build: {
                rollupOptions: {
                    external: [
                        "SvelteKitAuth"
                    ],
                },
            },
        },
    },
};

export default config;
Dan6erbond commented 3 years ago

Hi there! Thanks for creating this issue! I'm glad to see more people have been wanting such a library and that I could start working on something like this for SvelteKit!

To clarify: Do you dislike the usage of hooks.{js,ts} in a general implementation or the way SvelteKitAuth does it? If you're referring to the way I designed SvelteKitAuth, I'm open to alternate suggestions that might be more semantic and inline with the way things are otherwise handled in the Svelte ecosystem and would love to see contributions!

Configuring the Library

As for the svelte.config.js, no adjustments need to be made there, as it's simply a dependency that uses API routes as well as the hooks.{js,ts} files to provide endpoints and augment the session which provides all the client-side information to Svelte components.

I just started a enhancements/example-app branch where I am working on getting this setup as a proper dependency for a testing application and you can check out the branch to see how it's configured.

Initially, I was getting errors as well. Since the dependency is built on top of Typescript I had to make some adjustments to the build configuration. Those should be resolved now, though and you can run the example app to see how it's setup!

At the moment, only the getSession and handle methods are configured in the example app. Alongside the main lib/appAuth.ts configuration file where the instance of SvelteKitAuth is created. But you can see how to setup the API routes in the sister repository Cheeri-No. The gist of the setup is a src/routes/api/auth/[...auth].ts file that exports the get and post handlers provided by SvelteKitAuth:

import { appAuth } from "$lib/appAuth";

export const { get, post } = appAuth;

Disclaimer

SvelteKitAuth is still very much in beta. The JWTs are generated with a calculated signature key that can easily be reverse-engineered should you use this and publish the application online. Until configuration options are provided to use a secret key I cannot recommend integrating this in any production applications but as I am working on this for my own use as well those features are already a WIP.

SGarno commented 3 years ago

Awesome! Thanks for adding an example. I will check it out.

To answer your question. I like hooks.js in SvelteKit, but it really is really centered around hooking to the current application you are building, not really for when you are creating a wrapper for an external api.

It feels awkward to have to perform your external authentication outside of your library. In other words, you can't just do something like this:

export default class MyExternalAPI {

    #baseurl;
    #data;
    #sessionID;

    async Init (url) {
        this.#baseurl = url + "/api/v1/"

        let resp = await fetch(this.#baseurl + "getData", {
            method: 'GET',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
            },
            credentials: 'same-origin',
        })

        if (resp.status != 200) {
            console.log(`${url} => ${resp.status}:${resp.statusText}`)
            throw new Error(`Unable to connect to server at specified URL [${this.#baseurl}]`)
        }

        // Not possible in SvelteKit
        this.#sessionID = resp.headers.cookie('session_id')

        // Get response data
        this.#data = await resp.json()

    }

    // .....
}

Instead, you have to hook the credentialing outside of the library (as you have done here). To me....there needs to be a way to do this within SK that is more library friendly. Going outside of it and requiring an application level hook just feels wrong.

....And thus....why you created SvelteKitAuth 😄

Anyway....I will check out the sample app and see if I can get it running. I will keep you posted.

Dan6erbond commented 3 years ago

I see what you mean. I agree that the hooks in general should be oriented around the application's needs, and not be interfered with too much from external APIs.

Personally, I liked the way session is provided by SvelteKit from the $app/stores module, and I'm not yet sure how that can be recreated. So SvelteKitAuth essentially wraps the getSession hook and you're still free to augment the data with the session() callback, which is designed almost identically to its counterpart in NextAuth.js.

Of course, if you don't want SvelteKitAuth interferring with the SvelteKit session, it's possible to just use the /api/auth/session route it generates instead and fetch the session on load. But I'll look into ways to separate the two in the future!

I suppose it would be possible to make it a svelte.config.js plugin, but that comes with the possible downside that it loses some functionality with classes and environment variables, though I'm not sure there, either, what is supported and what isn't. The design I followed leans on NextAuth.js, with the main difference being the object-oriented approach, allowing far more customization than just adding OAuth2 and HTTP POST providers.

Providing this kind of customizable functionality with zero restrictions and full control to expand with your own authentication methods that might be less conventional than OAuth2 will be a strength of SvelteKit, but comes with the inherit drawback that it needs to be part of your application so you can add custom providers, set the callbacks, etc.

SGarno commented 3 years ago

Ok so I see where I was originally having dependency problems. Originally, I did an npm i https://github.com/Dan6erbond/SvelteKitAuth.git and tried to use it like a node_module, which didn't work (might be something wrong with package.json?).

I read your reply " it needs to be part of your application" and then embedded it within my $lib. That seemed to fix the dependency errors. However, I am now getting this error:

500
The requested module '/src/lib/SvelteKitAuth/interfaces.ts' does not provide an export named 'JWT'

SyntaxError: The requested module '/src/lib/SvelteKitAuth/interfaces.ts' does not provide an export named 'JWT'

It is somehow getting optimized out because I can see in interfaces.ts that it is exporting JWT as well as User and Session

appAuth.js

import SvelteKitAuth from "$lib/SvelteKitAuth";
import {
    OAuth2Provider
} from "$lib/SvelteKitAuth/providers";

export const appAuth = new SvelteKitAuth({
    providers: [
        new OAuth2Provider({
            profile (profile) {
                return { ...profile, provider: "oauth2" };
            },
        }),
    ],
    callbacks: {
        session (token, profile) {
            if (profile?.provider) {
                const { provider, ...account } = profile;
                token = {
                    ...token,
                    user: {
                        ...token.user,
                        [provider]: account,
                    },
                };
            }

            return token;
        },
    },
});

hooks.js

import { appAuth } from "$lib/appAuth";

export async function handle ({ request, render }) {
    // TODO https://github.com/sveltejs/kit/issues/1046
    if (request.query.has("_method")) {
        request.method = request.query.get("_method").toUpperCase();
    }
    const response = await render(request);
    return response;
};

export const { getSession } = appAuth;
SGarno commented 3 years ago

BTW: In your example app...NPM v7 has gotten rid of link:... in favor of file:.., so in your package.json, you need to change it to:

 "svelte-kit-auth": "file:../"

for anyone who is running later versions of NPM.

I am also getting the same dependency error that I was earlier until I embedded the source.

Error: Failed to resolve entry for package "svelte-kit-auth". The package may have incorrect main/module/exports specified in its package.json.
    at resolvePackageEntry (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:32563:15)
    at tryNodeResolve (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:32382:11)
    at Context.resolveId (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:32265:28)
    at Object.resolveId (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:44211:55)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async TransformContext.resolve (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:44016:23)
    at async normalizeUrl (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:68950:34)
    at async TransformContext.transform (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:69080:57)
    at async Object.transform (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:44268:30)
    at async transformRequest (D:\Development\svelte\SvelteKitAuth\app\node_modules\vite\dist\node\chunks\dep-e9a16784.js:58540:29)
Dan6erbond commented 3 years ago

Ok so I see where I was originally having dependency problems. Originally, I did an npm i https://github.com/Dan6erbond/SvelteKitAuth.git and tried to use it like a node_module, which didn't work (might be something wrong with package.json?).

Correct. Not only with the package.json, but there was some issues with the tsconfig.json as well, and some structural changes I had to make in the new branch in order to get it working as a Node module. Now the imports svelte-kit-auth, svelte-kit-auth/client and svelte-kit-auth/providers should work as expected.

It is somehow getting optimized out because I can see in interfaces.ts that it is exporting JWT as well as User and Session

That part is extremely odd. From the code provided I can't see you using JWT at all, especially since you're working in Javascript it should only give you type-hinting at best? But, I don't think that's something I'll have to look into much once I merge enhancements/example-app back into the main branch, which deals with using svelte-kit-auth as a proper Node module, as I said.

BTW: In your example app...NPM v7 has gotten rid of link:... in favor of file:.., so in your package.json, you need to change it to:

Thank you for the tip! I'll give that a look.

I am also getting the same dependency error that I was earlier until I embedded the source.

Hmm. Now this is something I'll have to look into. As you can see, in my example app with Yarn this worked flawlessly and the auth provider functions as expected. Do you mind explaining how exactly you installed it? Could it be a missing dist folder from directly cloning from git? Try running npm run build or yarn build before adding the link.

Dan6erbond commented 3 years ago

Small update: I just tried using link:../ instead of file:../, which resulted in a ton of errors with Vite. It seems building for this platform is a bit different than your standard Node modules, so I'll be looking into how other SvelteKit modules do this to hopefully find something better, but for now the standalone installation still seems broken!

SGarno commented 3 years ago

Yeah, I think you are seeing the same thing now. Re-cloned your latest updates, then did an npm install and finally a run build and you can see that the interfaces.js file is blank. There is something funky in the config that it is getting optimized out.

It works on your side? What NPM version are you using?

SGarno commented 3 years ago

BTW: I just noticed this PR....Wonder if that would make this process easier for your project? https://github.com/sveltejs/kit/pull/1465/commits/2974931a609c2dcde2fd7f7d2f5bb9dba3afc1f5

Dan6erbond commented 3 years ago

Yeah, I think you are seeing the same thing now. Re-cloned your latest updates, then did an npm install and finally a run build and you can see that the interfaces.js file is blank. There is something funky in the config that it is getting optimized out.

I'll have to see if using a bundler like microbundle mitigates these issues, otherwise, as I mentioned, I'll check what other SvelteKit packages use and try to adapt the package to those conventions.

It works on your side? What NPM version are you using?

Using Yarn and link: it works flawlessly, but file: installs it locally and that's where Vite throws errors as you can see in #10.

BTW: I just noticed this PR....Wonder if that would make this process easier for your project? sveltejs/kit@2974931

I doubt it. This seems more like an interceptor for the fetch() function that SvelteKit provides, whereas the hook that SvelteKitAuth highjacks is simply a RequestHandler and if you don't want to inject the data into the $app/stores session you can forego the step of assigning the hook in hooks.ts and instead fetch it manually (which I plan on adding as a feature down the road with a client package).

Dan6erbond commented 3 years ago

Small update before I close this issue: I've been able to create a bundle now using Rollup with the esbuild and multiple-inputs plugins as you can see in #11, which seems to work without issue using file:../ to install the lib in the demo app.

There are some new issues, though. SvelteKit does not provide access to the router and stores outside of the app itself, from what I can tell, I'll see how to get programmatic navigation, and a separate store working for SvelteKitAuth to use as the client. This means refactor most of the /client submodule, which shouldn't be an issue, as it will allow me to make it class-based and more configurable in case the endpoint changes from /api/auth to something else.

I will see about publishing the first NPM release today, and getting some CI and CD setup in the repository.