Skip to content

Commit 356b74b

Browse files
committed
added login with session cookie example
1 parent fb09fae commit 356b74b

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

example/index.ts

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Hono } from 'hono';
2-
import { Auth, emulatorHost, WorkersKVStoreSingle } from '../src';
2+
import { getCookie, setCookie } from 'hono/cookie';
3+
import { csrf } from 'hono/csrf';
4+
import { html } from 'hono/html';
5+
import { Auth, EmulatorCredential, emulatorHost, WorkersKVStoreSingle } from '../src';
36

47
type Env = {
58
EMAIL_ADDRESS: string;
@@ -52,4 +55,142 @@ app.post('/verify-header', async c => {
5255
});
5356
});
5457

58+
app.use('/admin/*', csrf());
59+
60+
app.get('/admin/login', async c => {
61+
const content = await html`<html>
62+
<head>
63+
<meta charset="UTF-8" />
64+
<title>Login</title>
65+
</head>
66+
<body>
67+
<h1>Login Page</h1>
68+
<button id="sign-in" type="button">Sign-In</button>
69+
<script type="module">
70+
// See https://firebase.google.com/docs/auth/admin/manage-cookies
71+
//
72+
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.5.0/firebase-app.js';
73+
import $ from 'https://cdn.skypack.dev/jquery';
74+
// Add Firebase products that you want to use
75+
import {
76+
getAuth,
77+
signInWithEmailAndPassword,
78+
onAuthStateChanged,
79+
connectAuthEmulator,
80+
signOut,
81+
setPersistence,
82+
inMemoryPersistence,
83+
} from 'https://www.gstatic.com/firebasejs/10.5.0/firebase-auth.js';
84+
const app = initializeApp({
85+
apiKey: 'test1234',
86+
authDomain: 'test',
87+
projectId: 'project12345',
88+
});
89+
const auth = getAuth(app);
90+
connectAuthEmulator(auth, 'http://127.0.0.1:9099');
91+
setPersistence(auth, inMemoryPersistence);
92+
93+
/**
94+
* @param {string} name The cookie name.
95+
* @return {?string} The corresponding cookie value to lookup.
96+
*/
97+
function getCookie(name) {
98+
const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
99+
return v ? v[2] : null;
100+
}
101+
102+
/**
103+
* @param {string} url The session login endpoint.
104+
* @param {string} idToken The ID token to post to backend.
105+
* @return {any} A jQuery promise that resolves on completion.
106+
*/
107+
function postIdTokenToSessionLogin(url, idToken) {
108+
// POST to session login endpoint.
109+
return $.ajax({
110+
type: 'POST',
111+
url: url,
112+
data: JSON.stringify({ idToken: idToken }),
113+
contentType: 'application/json',
114+
});
115+
}
116+
117+
$('#sign-in').on('click', function () {
118+
console.log('clicked');
119+
120+
signInWithEmailAndPassword(auth, '[email protected]', 'test1234')
121+
.then(({ user }) => {
122+
// Get the user's ID token as it is needed to exchange for a session cookie.
123+
const idToken = user.accessToken;
124+
// Session login endpoint is queried and the session cookie is set.
125+
// CSRF protection should be taken into account.
126+
// ...
127+
const csrfToken = getCookie('csrfToken');
128+
return postIdTokenToSessionLogin('/admin/login_session', idToken, csrfToken);
129+
})
130+
.then(() => {
131+
// A page redirect would suffice as the persistence is set to NONE.
132+
return signOut(auth);
133+
})
134+
.then(() => {
135+
window.location.assign('/admin/profile');
136+
});
137+
});
138+
</script>
139+
</body>
140+
</html>`;
141+
return c.html(content);
142+
});
143+
144+
app.post('/admin/login_session', async c => {
145+
const json = await c.req.json();
146+
const idToken = json.idToken;
147+
if (!idToken || typeof idToken !== 'string') {
148+
return c.json({ message: 'invalid idToken' }, 400);
149+
}
150+
// Set session expiration to 5 days.
151+
const expiresIn = 60 * 60 * 24 * 5 * 1000;
152+
// Create the session cookie. This will also verify the ID token in the process.
153+
// The session cookie will have the same claims as the ID token.
154+
// To only allow session cookie setting on recent sign-in, auth_time in ID token
155+
// can be checked to ensure user was recently signed in before creating a session cookie.
156+
const auth = Auth.getOrInitialize(
157+
c.env.PROJECT_ID,
158+
WorkersKVStoreSingle.getOrInitialize(c.env.PUBLIC_JWK_CACHE_KEY, c.env.PUBLIC_JWK_CACHE_KV),
159+
new EmulatorCredential() // You MUST use ServiceAccountCredential in real world
160+
);
161+
const sessionCookie = await auth.createSessionCookie(
162+
idToken,
163+
{
164+
expiresIn,
165+
},
166+
c.env // This valus must be removed in real world
167+
);
168+
setCookie(c, 'session', sessionCookie, {
169+
maxAge: expiresIn,
170+
httpOnly: true,
171+
// secure: true // set this in real world
172+
});
173+
return c.json({ message: 'success' });
174+
});
175+
176+
app.get('/admin/profile', async c => {
177+
const session = getCookie(c, 'session') ?? '';
178+
179+
const auth = Auth.getOrInitialize(
180+
c.env.PROJECT_ID,
181+
WorkersKVStoreSingle.getOrInitialize(c.env.PUBLIC_JWK_CACHE_KEY, c.env.PUBLIC_JWK_CACHE_KV),
182+
new EmulatorCredential() // You MUST use ServiceAccountCredential in real world
183+
);
184+
185+
try {
186+
const decodedToken = await auth.verifySessionCookie(
187+
session,
188+
c.env // This valus must be removed in real world
189+
);
190+
return c.json(decodedToken);
191+
} catch (err) {
192+
return c.redirect('/admin/login');
193+
}
194+
});
195+
55196
export default app;

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Credential } from './credential';
33
import type { KeyStorer } from './key-store';
44
import { WorkersKVStore } from './key-store';
55

6+
export { type Credential, ServiceAccountCredential, EmulatorCredential } from './credential';
67
export { emulatorHost, useEmulator } from './emulator';
78
export type { KeyStorer };
89
export type { EmulatorEnv } from './emulator';

0 commit comments

Comments
 (0)