Kanahiro / sveltekit-amplify-starter
SvelteKit + Amplify Gen2 (Hosting + Backend) Template
sveltekit-amplify-starter
SvelteKit + Amplify Gen2 (Hosting, Backend)
usage
- Copy or Fork this repository
- Connect your repository in the Amplify Console
- Done
tips
-
./src/routes/AmplifyInit.svelte automatically loads
./amplify_outputs.json
generated by Amplify CLI on build time or sandbox mode../amplify_outputs.json
should not be modified manually and so it is.gitignore
d. - Server Side Rendering (SSR) works good but streaming does not work.
references
TL;DR
- AWS Amplify can run SvelteKit with SSR.
- There is a implementation of SSR authentication for Next.js but not for SvelteKit.
- Is this article, I'll show you my implementation for SSR authentication.
SvelteKit and AWS Amplify
SvelteKit is a full-stack framework based on Svelte frontend library. SvelteKit supports server-side rendering (SSR). Next.js is in this field but I prefer SvelteKit because we can write very simple and efficient codes in SvelteKit, it's great.
AWS Amplify is service to build and deploy web application with some AWS services such as Cognito/Lambda/DynamoDB/S3 or so without deep knowledge about them.
In these days Amplify supports SSR and there are some examples to deploy SvelteKit with Amplify.
https://docs.aws.amazon.com/amplify/latest/userguide/get-started-sveltekit.html
It is nice we can deploy SvelteKit application only by connecting GitHub repo to Amplify. One point, we should note that SSR "Streaming" is not supported yet.
Amplify Gen2
https://docs.amplify.aws/javascript/
Actually, I'm not familiar with Amplify "Gen1". I talk about Gen2 in entrie this article.
Amplify Gen2 omits CLI command to add Amplify Backends(auth, data...). Instead of CLI, you can declaratively specify backends by codes in TypeScript and Gen2 provides us with "sandbox" feature, it enables us to launch backends only for your development, this is similar to "branching" in Supabase but it is interesting that Amplify make sandboxes for local development.
Amplify Auth
We can add authentication like this:
// amplify/auth/resource.ts
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
loginWith: {
email: true
}
});
// amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
const backend = defineBackend({
auth
});
Traditionally, you can access Amplify Auth(Cognito) from frontend only with following codes:
// initialization
import { Amplify } from 'aws-amplify';
import outputs from './amplify_outputs.json'; // generated by Amplify CLI
Amplify.configure(outputs);
// signin
import { signIn } from 'aws-amplify/auth'
await signIn({
username: "hello@mycompany.com",
password: "hunter2",
})
// credentials stored in LocalStorage
// ...perform authorized actions for backends with credentials
This approach have been standard in Amplify but in context of SSR we need better approach than this. Assuming we have public pages and private pages. Is SSR, server should return private pages only for authenticated users. To adjust authentication processes for SSR, we need to:
- send credentials from client to server
- verify credentials
- navigate only verified users to private pages
Adapter for Next.js
For Next.js, an "adapter" is officially provided by Amplify.
https://docs.amplify.aws/react/build-a-backend/server-side-rendering/
It is not for SvelteKit. Then I tried to write some codes.
Adapter for SvelteKit
The procedure to serve pages in SvelteKit is similar to in Next.js. I wrote adapter for SvelteKit quoting some codes of adapter-nextjs. The following is parts of them:
Initialization
import { Amplify } from 'aws-amplify';
import outputs from '../../amplify_outputs.json';
Amplify.configure(outputs, { ssr: true });
{ ssr: true }
means to use cookie for storing credentials instead of LocalStorage. Once signed in, credentials are wrote to cookie and they are sent to server on accessing any pages.
Response Hooks
// src/hooks.server.ts
import { redirect, type Handle } from '@sveltejs/kit';
import { fetchAuthSession } from 'aws-amplify/auth/server';
import { createRunWithAmplifyServerContext } from '$lib/adapter-sveltekit';
import outputs from '../amplify_outputs.json';
// init auth-checker with outputs once when the server starts
const runWithAmplifyServerContext = createRunWithAmplifyServerContext(outputs);
export const handle: Handle = async ({ event, resolve }): Promise<Response> => {
if (!event.url.pathname.startsWith('/private')) {
return resolve(event);
}
const authenticated = await runWithAmplifyServerContext({
event,
operation: async (contextSpec) => {
try {
const session = await fetchAuthSession(contextSpec);
return session.tokens?.accessToken !== undefined && session.tokens?.idToken !== undefined;
} catch (error) {
console.log(error);
return false;
}
}
});
if (!authenticated) {
redirect(303, '/');
} else {
return resolve(event);
}
};
-
handle()
inhooks.server.ts
is called on before every response. Only verified users can access pages under/private
routes. - The signatures are designed to mimic adapter-nextjs
$lib/adapter-sveltekit
is the most important parts, let's go next.
adapter-sveltekit
src/lib/adapter-sveltekit/
├── createCookieStorage.ts
├── createRunWithAmplifyServerContext.ts
├── createTokenValidator.ts
├── index.ts
└── isValidCognitoToken.ts
There some files but createRunWithAmplifyServerContext.ts
is the essential part of this module.
// src/lib/adapter-sveltekit/createRunWithAmplifyServerContext.ts
import type { RequestEvent } from '@sveltejs/kit';
import {
createAWSCredentialsAndIdentityIdProvider,
createKeyValueStorageFromCookieStorageAdapter,
createUserPoolsTokenProvider,
runWithAmplifyServerContext as runWithAmplifyServerContextCore,
type AmplifyOutputs,
type AmplifyServer
} from 'aws-amplify/adapter-core';
import { sharedInMemoryStorage, parseAmplifyConfig } from 'aws-amplify/utils';
import { createTokenValidator } from './createTokenValidator';
import { createCookieStorage } from './createCookieStorage';
type RunWithAmplifyServerContextOptions = {
event: RequestEvent | null;
operation: (contextSpec: AmplifyServer.ContextSpec) => boolean | Promise<boolean>;
};
function createRunWithAmplifyServerContext(outputs: AmplifyOutputs) {
const resourcesConfig = parseAmplifyConfig(outputs);
const runWithAmplifyServerContext = async ({
event,
operation
}: RunWithAmplifyServerContextOptions) => {
const keyValueStorage = event
? createKeyValueStorageFromCookieStorageAdapter(
createCookieStorage(event.cookies),
createTokenValidator({
userPoolId: resourcesConfig.Auth?.Cognito?.userPoolId,
userPoolClientId: resourcesConfig.Auth?.Cognito?.userPoolClientId
})
)
: sharedInMemoryStorage;
const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(
resourcesConfig.Auth!,
keyValueStorage
);
const tokenProvider = createUserPoolsTokenProvider(resourcesConfig.Auth!, keyValueStorage);
return await runWithAmplifyServerContextCore(
resourcesConfig,
{
Auth: { credentialsProvider, tokenProvider }
},
operation
);
};
return runWithAmplifyServerContext;
}
export { createRunWithAmplifyServerContext };
- This codes mimic Next.js one.
- Accept
RequestEvent
in SvelteKit instead of "Context" in Next.js
Workaround for signOut
aws-amplify/auth
provides signOut()
but this doesn't work for SSR mode, it might discard only LocalStorage. The cookies wrote by Amplify seems to have "HttpOnly" so we have to revoke cookies from server.
// src/routes/signin/+page.svelte
<form method="POST" action="?/signOut">
<button type="submit">Sign Out</button>
</form>
// src/routes/signin/+page.server.ts
export const actions = {
signOut: async ({ cookies }) => {
// remove all cookies startsWith "CognitoIdentityServiceProvider"
const cognitoCookies = cookies
.getAll()
.filter((cookie) => cookie.name.startsWith('CognitoIdentityServiceProvider'));
for (const cookie of cognitoCookies) {
cookies.set(cookie.name, '', { maxAge: 0, path: '/' });
}
}
};
This is a good case to use form action in SvelteKit. By sending POST request via form and invoke an action named as "signOut", server overwrites the cookie as "expired". We can find cookies of Amplify by checking prefix "CognitoIdentityServiceProvider".
Perfect! You can authenticate users in SSR context in SvelteKit!.
Conclusion
I shown that SvelteKit can be used in Amplify, with SSR authentication. SvelteKit is the great framework and I hope that Amplify, which deploy applications easily, can support SvelteKit more.
The all codes which can be launched are here:
https://github.com/Kanahiro/sveltekit-amplify-starter
Future works
- Auth UI for Svelte like React or Vue.
Top comments (0)