Dejan Lukić

Creating a Simple OAuth2 Flow in Deno

Originally written for Deno.

Concepts

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.

#deno #oauth2