Creating a Simple OAuth2 Flow in Deno
Originally written for Deno.
Concepts
- Use
oauth2_client
to verify the callback code. - Use session to store the user, as well as to destroy their session (logout).
Overview
Session storage allows you to store data in the browser. In this case, you will store user data allowing you to perform backend requests.
Login route
Start by importing the dependencies you defined previously in the main file.
routes/index.ts
import { AppState, oauth2Client } from '../main.ts';
import { create } from "https://deno.land/x/djwt@v2.9/mod.ts"
Continue to create the /login
route. The route will be unprotected, as it doesn’t allow the user to view the sensitive data.
router.get('/login', async (ctx: Context) => {
// Construct the URL for the authorization redirect and get a PKCE codeVerifier
const { uri, codeVerifier } = await oauth2Client.code.getAuthorizationUri();
// Store both the state and codeVerifier in the user session
ctx.state.session.flash('codeVerifier', codeVerifier);
// Redirect the user to the authorization endpoint
ctx.response.redirect(uri);
})
oauth2client.code.getAuthorizationUri()
method constructs the URL for the authorization redirect from the provided config in the oauth2client
and generates a Proof Key for Code Exchange (PKCE) codeVerifier
.
PKCE is an extension to the OAuth 2.0 protocol that provides a method to secure authorization codes in public clients, typically single-page apps or mobile apps, where the client secret can't be safely stored. It mitigates the risk of an authorization code interception attack by introducing a one-time secret created by the calling application and sent in the authorization request, which is verified by the authorization server when the authorization code is exchanged for an access token.
The constructed URL will point to the OAuth2 provider's authorization endpoint, and it will include parameters like the client ID, requested scopes, and a generated state value for CSRF protection.
Then, codeVerifier
is stored in the user’s session using the ctx.state.session.flash()
method. This is done so that it can be retrieved later when the authorization code is exchanged for an access token.
Finally, the user is redirected to the authorization endpoint using ctx.response.redirect()
method
Logout Route
Continue to create the /logout
route. The route will be unprotected.
routes/index.ts
router.get('/logout', async (ctx: Context) => {
// Destroy the user session
await ctx.state.session.deleteSession();
ctx.cookies.delete('user');
ctx.cookies.delete('session');
ctx.response.redirect('http://localhost:3000/');
return;
});
ctx.state.session.deleteSession()
will delete (destroy) the user session, the next two methods ctx.cookies.delete()
will delete both the user
and the session
from the cookies, so the user is properly logged out.
Finally, the route will redirect the user to the homepage.
Test the code
To test the implementation, navigate to http://localhost:8000/login
. You will be redirected to the GitHub OAuth2 provider to authenticate your profile.
Upon authentication, you will be redirected back to the application to the calllback endpoint specified in the GitHub OAuth2 application.