@hono/oauth-providers
v0.6.2
Published
Social login for Hono JS, integrate authentication with facebook, github, google and linkedin to your projects.
Downloads
2,022
Readme
OAuth Providers Middleware
Authentication middleware for Hono. This package offers a straightforward API for social login with platforms such as Facebook, GitHub, Google, LinkedIn and X(Twitter).
Installation
You can install hono
and @hono/oauth-providers
via npm.
npm i hono @hono/oauth-providers
Usage
Open Auth simplifies the OAuth2 flow, enabling you to utilize social login with just a single method.
On every platform you choose to add to your project you have to add on its platform the callback uri or redirect uri. Open Auth handles the redirect uri internally as the route you are using the middleware on, so if you decide to use the google auth on the route /api/v1/auth/google/
the redirect uri will be DOMAIN/api/v1/auth/google
.
app.use(
"api/v1/auth/google", // -> redirect_uri by default
googleAuth({ ... })
)
Also, there is two ways to use this middleware:
app.use(
'/google',
googleAuth({
client_id: Bun.env.GOOGLE_ID,
client_secret: Bun.env.GOOGLE_SECRET,
scope: ['openid', 'email', 'profile'],
})
)
app.get('/google', (c) => {
const token = c.get('token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-google')
return c.json({
token,
grantedScopes,
user,
})
})
export default app
Or
app.get(
'/google',
googleAuth({
client_id: Bun.env.GOOGLE_ID,
client_secret: Bun.env.GOOGLE_SECRET,
scope: ['openid', 'email', 'profile'],
}),
(c) => {
const token = c.get('token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-google')
return c.json({
token,
grantedScopes,
user,
})
}
)
export default app
import { Hono } from 'hono'
import { googleAuth } from '@hono/oauth-providers/google'
const app = new Hono()
app.use(
'/google',
googleAuth({
client_id: Bun.env.GOOGLE_ID,
client_secret: Bun.env.GOOGLE_SECRET,
scope: ['openid', 'email', 'profile'],
})
)
export default app
Parameters
client_id
:- Type:
string
. Required
.- Your app client ID. You can find this value in the API Console Credentials page. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asGOOGLE_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.- Your app client secret. You can find this value in the API Console Credentials page. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asGOOGLE_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes Google offers for utilizing their API on the OAuth 2.0 Scopes page.
If your app is not verified by Google, the accessible scopes for your app are significantly limited.
- Type:
login_hint
:- Type:
string
. Optional
.- Set the parameter value to an email address or
sub
identifier to provide a hint to the Google Authentication Server who is asking for authentication.
- Type:
prompt
:- Type:
string
. Optional
.- Define the prompt the user will receive when logging into their Google account. If not sent, the user will only be prompted the first time your project requests access. Choose one of the following options:
none
: Do not display any authentication or consent screens. Must not be specified with other values.consent
: Prompt the user for consent.select_account
: Prompt the user to select an account.
- Type:
Authentication Flow
After the completion of the Google OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
googleAuth
method provides 3 set key data:
token
:- Access token to make requests to the google API for retrieving user information and performing actions on their behalf.
- Type:
{ token: string expires_in: number }
granted-scopes
:- If the
include_granted_scopes
parameter was set totrue
, you can find here the scopes for which the user has granted permissions. - Type:
string[]
.
- If the
user-google
:- User basic info retrieved from Google
- Type:
{ id: string email: string verified_email: boolean name: string given_name: string family_name: string picture: string locale: string }
To access this data, utilize the c.get
method within the callback of the upcoming HTTP request handler.
app.get('/google', (c) => {
const token = c.get('token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-google')
return c.json({
token,
grantedScopes,
user,
})
})
Revoke Token
In certain use cases, you may need to programmatically revoke a user's access token. In such scenarios, you can utilize the revokeToken
method, which accepts the token
to be revoked as its unique parameter.
import { googleAuth, revokeToken } from '@hono/oauth-providers/google'
app.post('/remove-user', async (c, next) => {
await revokeToken(USER_TOKEN)
// ...
})
import { Hono } from 'hono'
import { facebookAuth } from '@hono/oauth-providers/facebook'
const app = new Hono()
app.use(
'/facebook',
facebookAuth({
client_id: Bun.env.FACEBOOK_ID,
client_secret: Bun.env.FACEBOOK_SECRET,
scope: ['email', 'public_profile'],
fields: [
'email',
'id',
'first_name',
'last_name',
'middle_name',
'name',
'picture',
'short_name',
],
})
)
export default app
Parameters
client_id
:- Type:
string
. Required
.- Your app client ID. You can find this value in the App Dashboard Dashboard page. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asFACEBOOK_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.- Your app client secret. You can find this value in the App Dashboard Dashboard page. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asFACEBOOK_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes Facebook offers for utilizing their API on the Permissions page.
If your app is not verified by Facebook, the accessible scopes for your app are significantly limited.
- Type:
fields
:- Type:
string[]
. - Fields you request from the Facebook API to be sent once the user has logged in. You can find a comprehensive reference for all the fields you can request on the Facebook User Reference page.
- Type:
Authentication Flow
After the completion of the Facebook OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
facebookAuth
method provides 3 set key data:
token
:- Access token to make requests to the Facebook API for retrieving user information and performing actions on their behalf. It has a duration of 60 days.
- Type:
{ token: string expires_in: number }
granted-scopes
:- If the
include_granted_scopes
parameter was set totrue
, you can find here the scopes for which the user has granted permissions. - Type:
string[]
.
- If the
user-facebook
:- User basic info retrieved from Facebook
- Type:
{ id: string name: string email: string picture: { data: { height: number is_silhouette: boolean url: string width: number } } first_name: string last_name: string short_name: string }
To access this data, utilize the c.get
method within the callback of the upcoming HTTP request handler.
app.get('/facebook', (c) => {
const token = c.get('token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-facebook')
return c.json({
token,
grantedScopes,
user,
})
})
GitHub
GitHub provides two types of Apps to utilize its API: the GitHub App
and the OAuth App
. To understand the differences between these apps, you can read this article from GitHub, helping you determine the type of App you should select.
Parameters
client_id
:- Type:
string
. Required
.Github App
andOauth App
.- Your app client ID. You can find this value in the GitHub App settings or the OAuth App settings based on your App type. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asGITHUB_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.Github App
andOauth App
.- Your app client secret. You can find this value in the GitHub App settings or the OAuth App settings based on your App type. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asGITHUB_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.Oauth App
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes Github offers for utilizing their API on the Permissions page. For
GitHub Apps
, you select the scopes during the App creation process or in the settings.
- Type:
oauthApp
:- Type:
boolean
. Required
.Oauth App
.- Set this value to
true
if your App is of the OAuth App type. Defaults tofalse
.
- Type:
Authentication Flow
After the completion of the Github Auth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
githubAuth
method provides 4 set key data:
token
:- Access token to make requests to the Github API for retrieving user information and performing actions on their behalf.
- Type:
{ token: string expires_in: number // -> only available for Oauth Apps }
refresh-token
:- You can refresh new tokens using this token, which has a longer lifespan. Only available for Oauth Apps.
- Type:
{ token: string expires_in: number }
user-github
:- User basic info retrieved from Github
- Type:
{ login: string id: number node_id: string avatar_url: string gravatar_id: string url: string html_url: string followers_url: string following_url: string gists_url: string starred_url: string subscriptions_url: string organizations_url: string repos_url: string events_url: string received_events_url: string type: string site_admin: boolean name: string company: string blog: string location: string email: string | null hireable: boolean | null bio: string twitter_username: string public_repos: number public_gists: number followers: number following: number created_at: string updated_at: string private_gists: number, // -> Github App total_private_repos: number, // -> Github App owned_private_repos: number, // -> Github App disk_usage: number, // -> Github App collaborators: number, // -> Github App two_factor_authentication: boolean, // -> Github App plan: { name: string, space: number, collaborators: number, private_repos: number } // -> Github App }
granted-scopes
:- If the
include_granted_scopes
parameter was set totrue
, you can find here the scopes for which the user has granted permissions.
- If the
Github App Example
import { Hono } from 'hono'
import { githubAuth } from '@hono/oauth-providers/github'
const app = new Hono()
app.use(
'/github',
githubAuth({
client_id: Bun.env.GITHUB_ID,
client_secret: Bun.env.GITHUB_SECRET,
})
)
app.get('/github', (c) => {
const token = c.get('token')
const user = c.get('user-github')
return c.json({
token,
user,
})
})
export default app
OAuth App Example
import { Hono } from 'hono'
import { githubAuth } from '@hono/oauth-providers/github'
const app = new Hono()
app.use(
'/github',
githubAuth({
client_id: Bun.env.GITHUB_ID,
client_secret: Bun.env.GITHUB_SECRET,
scope: ['public_repo', 'read:user', 'user', 'user:email', 'user:follow'],
oauthApp: true,
})
)
app.get('/github', (c) => {
const token = c.get('token')
const refreshToken = c.get('refresh-token')
const user = c.get('user-github')
return c.json({
token,
refreshToken,
user,
})
})
export default app
LinkedIn provides two types of Authorization to utilize its API: the Member Authotization
and the Application Authorization
. To understand the differences between these authorization methods, you can read this article from LinkedIn, helping you determine the type of Authorization your app should use.
Parameters
client_id
:- Type:
string
. Required
.Member
andApplication
authorization.- Your app client ID. You can find this value in the LinkedIn Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asLINKEDIN_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.Member
andApplication
authorization.- Your app client secret. You can find this value in the LinkedIn Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asLINKEDIN_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.Member Authorization
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes LinkedIn offers for utilizing their API on the Getting Access docs page.
- Type:
appAuth
: - Type:boolean
. -Required
. -Application Authorization
. - Set this value totrue
if your App uses the App Authorization method. Defaults tofalse
.To access the Application Authorization method you have to ask LinkedIn for It. Apparently you have to verify your app then ask for access.
Authentication Flow
After the completion of the LinkedIn Auth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
linkedinAuth
method provides 4 set key data:
token
:- Access token to make requests to the LinkedIn API for retrieving user information and performing actions on their behalf.
- Type:
{ token: string expires_in: number }
refresh-token
:- You can refresh new tokens using this token, which has a longer lifespan. Only available for Member Authorization.
- Type:
{ token: string expires_in: number }
user-linkedin
:- User basic info retrieved from LinkedIn.
- Type:
{ sub: string email_verified: boolean name: string locale: { country: string language: string }, given_name: string family_name: string email: string picture: string }
Only available for Member Authorization.
granted-scopes
:- If the
include_granted_scopes
parameter was set totrue
, you can find here the scopes for which the user has granted permissions.
- If the
Member Authentication Example
import { Hono } from 'hono'
import { linkedinAuth } from '@hono/oauth-providers/linkedin'
const app = new Hono()
app.use(
'/linkedin',
linkedinAuth({
client_id: Bun.env.LINKEDIN_ID,
client_secret: Bun.env.LINKEDIN_SECRET,
scope: ['email', 'openid', 'profile'],
})
)
app.get('/linkedin', (c) => {
const token = c.get('token')
const user = c.get('user-linkedin')
return c.json({
token,
user,
})
})
export default app
Application Example
import { Hono } from 'hono'
import { linkedinAuth } from '@hono/oauth-providers/linkedin'
const app = new Hono()
app.use(
'/linkedin',
linkedinAuth({
client_id: Bun.env.LINKEDIN_ID,
client_secret: Bun.env.LINKEDIN_SECRET,
appAuth: true,
})
)
app.get('/linkedin', (c) => {
const token = c.get('token')
return c.json(token)
})
export default app
Revoke Token
In certain use cases, you may need to programmatically revoke a user's access token. In such scenarios, you can utilize the revokeToken
method.
Parameters:
client_id
:string
.
- client_secret:
string
.
refresh_token
:string
.
Return Value:
token
:string
.
import { linkedinAuth, refreshToken } from '@hono/oauth-providers/linkedin'
app.post('linkedin/refresh-token', async (c, next) => {
const token = await refreshToken(LINKEDIN_ID, LINKEDIN_SECRET, USER_REFRESH_TOKEN)
// ...
})
X (Twitter)
import { Hono } from 'hono'
import { xAuth } from '@hono/oauth-providers/x'
const app = new Hono()
app.use(
'/x',
xAuth({
client_id: Bun.env.X_ID,
client_secret: Bun.env.X_SECRET,
scope: ['tweet.read', 'users.read', 'offline.access'],
fields: ['profile_image_url', 'url'],
})
)
export default app
Parameters
client_id
:- Type:
string
. Required
.- Your app client ID. You can find this value in the Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asX_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.- Your app client secret. You can find this value in the Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asX_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes X(Twitter) offers for utilizing their API on the Documentation. If not sent the default fields x set are
id
,name
andusername.
- Type:
fields
:- Type:
string[]
. Optional
.- Set of fields of the user information that can be retreived from X. Check All the fields available on the get user me reference.
- Type:
Authentication Flow
After the completion of the X OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
xAuth
method provides 4 set key data:
token
:- Access token to make requests to the x API for retrieving user information and performing actions on their behalf.
- Type:
{ token: string expires_in: number }
refresh-token
:- You can refresh new tokens using this token. The duration of this token is not specified on the X docs.
- Type:
{ token: string expires_in: number }
granted-scopes
:- Scopes for which the user has granted permissions.
- Type:
string[]
.
user-x
:- User basic info retrieved from X
- Type:
{ created_at: string description: string entities: { url: { urls: { start: number end: number url: string expanded_url: string display_url: string } } } id: string location: string most_recent_tweet_id: string name: string profile_image_url: string protected: boolean public_metrics: { followers_count: number following_count: number tweet_count: number listed_count: number like_count: number } url: string username: string verified_type: string verified: boolean }
If you want to receive the refresh token you must add the
offline.access
in the scopes parameter. To access this data, utilize thec.get
method within the callback of the upcoming HTTP request handler.
app.get('/x', (c) => {
const token = c.get('token')
const refreshToken = c.get('refresh-token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-x')
return c.json({
token,
refreshToken
grantedScopes,
user,
})
})
Refresh Token
Once the user token expires you can refresh their token wihtout the need to prompt the user again for access. In such scenario, you can utilize the refreshToken
method, which accepts the client_id
, client_secret
and refresh_token
as parameters.
The
refresh_token
can be used once. Once the token is refreshed X gives you a newrefresh_token
along with the new token.
import { xAuth, refreshToken } from '@hono/oauth-providers/x'
app.post('/x/refresh', async (c, next) => {
await refreshToken(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)
// ...
})
Revoke Token
In certain use cases, you may need to programmatically revoke a user's access token. In such scenarios, you can utilize the revokeToken
method, the client_id
, client_secret
and the token
to be revoked as parameters.
It returns a boolean
to tell whether the token was revoked or not.
import { xAuth, revokeToken } from '@hono/oauth-providers/x'
app.post('/remove-user', async (c, next) => {
await revokeToken(CLIENT_ID, CLIENT_SECRET, USER_TOKEN)
// ...
})
Discord
import { Hono } from 'hono'
import { discordAuth } from '@hono/oauth-providers/discord'
const app = new Hono()
app.use(
'/discord',
discordAuth({
client_id: Bun.env.DISCORD_ID,
client_secret: Bun.env.DISCORD_SECRET,
scope: ['identify', 'email'],
})
)
export default app
Parameters
client_id
:- Type:
string
. Required
.- Your app client ID. You can find this value in the Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asDISCORD_ID=
.
- Type:
client_secret
:- Type:
string
. Required
.- Your app client secret. You can find this value in the Developer Portal. When developing Cloudflare Workers, there's no need to send this parameter. Just declare it in the
wrangler.toml
file asDISCORD_SECRET=
.⚠️ Do not share your client secret to ensure the security of your app.
- Type:
scope
:- Type:
string[]
. Required
.- Set of permissions to request the user's authorization to access your app for retrieving user information and performing actions on their behalf. Review all the scopes Discord offers for utilizing their API on the Documentation.
- Type:
Authentication Flow
After the completion of the Discord OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
discordAuth
method provides 4 set key data:
token
:- Access token to make requests to the Discord API for retrieving user information and performing actions on their behalf.
- Type:
{ token: string expires_in: number }
refresh-token
:- You can refresh new tokens using this token. The duration of this token is not specified on the Discord docs.
- Type:
{ token: string expires_in: number }
> [!NOTE] > The refresh token Discord retrieves no implicit expiration
granted-scopes
:- Scopes for which the user has granted permissions.
- Type:
string[]
.
user-discord
:- User basic info retrieved from Discord
- Type:
{ id: string username: string avatar: string discriminator: string public_flags: number premium_type: number flags: number banner: string | null accent_color: string | null global_name: string avatar_decoration_data: string | null banner_color: string | null }
[!NOTE] To access this data, utilize the
c.get
method within the callback of the upcoming HTTP request handler.
app.get('/discord', (c) => {
const token = c.get('token')
const refreshToken = c.get('refresh-token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-discord')
return c.json({
token,
refreshToken
grantedScopes,
user,
})
})
Refresh Token
Once the user token expires you can refresh their token wihtout the need to prompt the user again for access. In such scenario, you can utilize the refreshToken
method, which accepts the client_id
, client_secret
and refresh_token
as parameters.
[!NOTE] The
refresh_token
can be used once. Once the token is refreshed Discord gives you a newrefresh_token
along with the new token.
import { discordAuth, refreshToken } from '@hono/oauth-providers/discord'
app.post('/discord/refresh', async (c, next) => {
const newTokens = await refreshToken(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)
// newTokenes = {
// token_type: 'bear',
// access_token: 'skbjbfhj3b4348wdvbwje239'
// expires_in: 60000
// refresh_token: 'sfcb0dwd0hdeh29db'
// scope: "identify email"
// }
// ...
})
Revoke Token
In certain use cases, you may need to programmatically revoke a user's access token. In such scenarios, you can utilize the revokeToken
method, the client_id
, client_secret
and the token
to be revoked as parameters.
It returns a boolean
to tell whether the token was revoked or not.
import { discordAuth, revokeToken } from '@hono/oauth-providers/discord'
app.post('/remove-user', async (c, next) => {
const revoked = await revokeToken(CLIENT_ID, CLIENT_SECRET, USER_TOKEN)
// revoked = true | false
// ...
})
Advance Usage
Customize redirect_uri
All the provider middlewares also accept a redirect_uri
parameter that overrides the default redirect_uri = c.req.url
behavior.
This parameters can be useful if
hono
process cannot infer correct redirect_uri from the request. For example, when the server runs behind a reverse proxy and have no access to its internet hostname.- Or, in need to start oauth flow from a different route.
- Or, in need to encode more info into
redirect_uri
.
const app = new Hono();
const SITE_ORIGIN = `https://my-site.com`;
const OAUTH_CALLBACK_PATH = `/oauth/google`;
app.get('/*',
async (c, next) => {
const session = readSession(c);
if (!session) {
// start oauth flow
const redirectUri = `${SITE_ORIGIN}${OAUTH_CALLBACK_PATH}?redirect=${encodeURIComponent(c.req.path)}`;
const oauth = googleAuth({ redirect_uri: redirectUri, ...more });
return await oauth(c, next)
}
},
async (c, next) => {
// if we are here, the req should contain either a valid session or a valid auth code
const session = readSession(c);
const authedGoogleUser = c.get('user-google')
if (authedGoogleUser) {
await saveSession(c, authedGoogleUser);
} else if (!session) {
throw new HttpException(401)
}
return next();
},
async (c, next) => {
// serve protected content
}
);
Author
monoald https://github.com/monoald
License
MIT
Contribute
If you want to add new providers, features or solve some bugs don't doubt to create an issue or make a PR.
For testing purposes run the following code in the parent folder (middleware/
):