Skip to content
Open
5 changes: 5 additions & 0 deletions .changeset/cyan-balloons-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/oauth-providers': minor
---

Allow for access_type to be passed for "offline" mode, defaults to "online". Allowing for refresh tokens to be used in subsequent requests
5 changes: 5 additions & 0 deletions .changeset/green-files-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hono/oauth-providers': minor
---

Allow for an optional state arg to be passed to Google Auth middleware
58 changes: 51 additions & 7 deletions packages/oauth-providers/src/providers/google/authFlow.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { HTTPException } from 'hono/http-exception'

import type { Token } from '../../types'
import { toQueryParams } from '../../utils/objectToQuery'
import type { GoogleErrorResponse, GoogleTokenResponse, GoogleUser } from './types'
import type { GoogleErrorResponse, GoogleTokenResponse, GoogleUser, Token } from './types'

type GoogleAuthFlow = {
client_id: string
Expand All @@ -14,7 +13,8 @@
state?: string
login_hint?: string
prompt?: 'none' | 'consent' | 'select_account'
access_type?: 'offline' | 'online'
access_type?: 'offline' | 'online',
refresh_token?: string
}

export class AuthFlow {
Expand All @@ -28,9 +28,9 @@
state: string | undefined
login_hint: string | undefined
prompt: 'none' | 'consent' | 'select_account' | undefined
access_type: 'online' | 'offline' | undefined
user: Partial<GoogleUser> | undefined
granted_scopes: string[] | undefined
access_type: 'offline' | 'online' | undefined

constructor({
client_id,
Expand All @@ -53,6 +53,7 @@
this.state = state
this.code = code
this.token = token
this.access_type = access_type
this.user = undefined
this.granted_scopes = undefined
this.access_type = access_type
Expand Down Expand Up @@ -106,7 +107,7 @@
if ('access_token' in response) {
this.token = {
token: response.access_token,
expires_in: response.expires_in,
expires_in: response.expires_in
}

this.granted_scopes = response.scope.split(' ')
Expand All @@ -120,8 +121,14 @@
}
}

async getUserData(): Promise<void> {
await this.getTokenFromCode()
async getUserData() {

// Check if token is expired and refresh if necessary
if ( this.access_type === 'offline' && this.isTokenExpired() ) {
await this.refreshToken()
} else {
await this.getTokenFromCode()
}

const response = (await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: {
Expand All @@ -137,4 +144,41 @@
this.user = response
}
}

async refreshToken() {
if (!this.token?.refresh_token) {

Check failure on line 149 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.

Check failure on line 149 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.
throw new HTTPException(400, { message: 'Refresh token not found' })
}

const response = (await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'content-type': 'application/json',
accept: 'application/json',
},
body: JSON.stringify({
client_id: this.client_id,
client_secret: this.client_secret,
refresh_token: this.token.refresh_token,

Check failure on line 162 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.

Check failure on line 162 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.
grant_type: 'refresh_token',
}),
}).then((res) => res.json())) as GoogleTokenResponse | GoogleErrorResponse

if ('error' in response) {
throw new HTTPException(400, { message: response.error_description })
}

if ('access_token' in response) {
this.token.token = response.access_token
this.token.expires_in = response.expires_in
this.token.refresh_token = response.refresh_token

Check failure on line 174 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.

Check failure on line 174 in packages/oauth-providers/src/providers/google/authFlow.ts

View workflow job for this annotation

GitHub Actions / build

Property 'refresh_token' does not exist on type 'Token'.
}
}

isTokenExpired() {
const currentTime = Math.floor(Date.now() / 1000)
return currentTime >= (this.token?.expires_in || 0)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function googleAuth(options: {
token: c.req.query('access_token') as string,
expires_in: Number(c.req.query('expires-in')) as number,
},
refresh_token: c.req.query('refresh_token') as string,
})

// Redirect to login dialog
Expand Down
5 changes: 5 additions & 0 deletions packages/oauth-providers/src/providers/google/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ export type GoogleUser = {
picture: string
locale: string
}

export type Token = {
token: string
expires_in: number
}
Loading