
Security News
Crates.io Users Targeted by Phishing Emails
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
@hono/oauth-providers
Advanced tools
Social login for Hono JS, integrate authentication with facebook, github, google and linkedin to your projects.
Authentication middleware for Hono. This package offers a straightforward API for social login with platforms such as Facebook, GitHub, Google, LinkedIn and X(Twitter).
You can install hono
and @hono/oauth-providers
via npm.
npm i hono @hono/oauth-providers
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
client_id
:
string
.Required
.wrangler.toml
file as GOOGLE_ID=
.client_secret
:
string
.Required
.wrangler.toml
file as GOOGLE_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.If your app is not verified by Google, the accessible scopes for your app are significantly limited.
login_hint
:
string
.Optional
.sub
identifier to provide a hint to the Google Authentication Server who is asking for authentication.prompt
:
string
.Optional
.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.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
:
{
token: string
expires_in: number
}
granted-scopes
:
include_granted_scopes
parameter was set to true
, you can find here the scopes for which the user has granted permissions.string[]
.user-google
:
{
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,
})
})
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
client_id
:
string
.Required
.wrangler.toml
file as FACEBOOK_ID=
.client_secret
:
string
.Required
.wrangler.toml
file as FACEBOOK_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.If your app is not verified by Facebook, the accessible scopes for your app are significantly limited.
fields
:
string[]
.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
:
{
token: string
expires_in: number
}
granted-scopes
:
include_granted_scopes
parameter was set to true
, you can find here the scopes for which the user has granted permissions.string[]
.user-facebook
:
{
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 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.
client_id
:
string
.Required
.Github App
and Oauth App
.wrangler.toml
file as GITHUB_ID=
.client_secret
:
string
.Required
.Github App
and Oauth App
.wrangler.toml
file as GITHUB_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.Oauth App
.GitHub Apps
, you select the scopes during the App creation process or in the settings.oauthApp
:
boolean
.Required
.Oauth App
.true
if your App is of the OAuth App type. Defaults to false
.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
:
{
token: string
expires_in: number // -> only available for Oauth Apps
}
refresh-token
:
{
token: string
expires_in: number
}
user-github
:
{
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
:
include_granted_scopes
parameter was set to true
, you can find here the scopes for which the user has granted permissions.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
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.
client_id
:
string
.Required
.Member
and Application
authorization.wrangler.toml
file as LINKEDIN_ID=
.client_secret
:
string
.Required
.Member
and Application
authorization.wrangler.toml
file as LINKEDIN_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.Member Authorization
.appAuth
: - Type: boolean
. - Required
. - Application Authorization
. - Set this value to true
if your App uses the App Authorization method. Defaults to false
.
To access the Application Authorization method you have to ask LinkedIn for It. Apparently you have to verify your app then ask for access.
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
:
{
token: string
expires_in: number
}
refresh-token
:
{
token: string
expires_in: number
}
user-linkedin
:
{
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
:
include_granted_scopes
parameter was set to true
, you can find here the scopes for which the user has granted permissions.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
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
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
.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)
// ...
})
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
client_id
:
string
.Required
.wrangler.toml
file as X_ID=
.client_secret
:
string
.Required
.wrangler.toml
file as X_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.id
, name
and username.
fields
:
string[]
.Optional
.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
:
{
token: string
expires_in: number
}
refresh-token
:
{
token: string
expires_in: number
}
granted-scopes
:
string[]
.user-x
:
{
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,
})
})
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)
// ...
})
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)
// ...
})
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
client_id
:
string
.Required
.wrangler.toml
file as DISCORD_ID=
.client_secret
:
string
.Required
.wrangler.toml
file as DISCORD_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.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
:
{
token: string
expires_in: number
}
refresh-token
:
{
token: string
expires_in: number
}
> [!NOTE]
> The refresh token Discord retrieves no implicit expiration
granted-scopes
:
string[]
.user-discord
:
{
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,
})
})
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"
// }
// ...
})
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
// ...
})
import { Hono } from 'hono'
import { twitchAuth } from '@hono/oauth-providers/twitch'
const app = new Hono()
app.use(
'/twitch',
twitchAuth({
client_id: Bun.env.TWITCH_ID,
client_secret: Bun.env.TWITCH_SECRET,
scope: ['user:read:email', 'channel:read:subscriptions', 'bits:read'],
redirect_uri: 'http://localhost:3000/twitch',
})
)
export default app
client_id
:
string
.Required
.wrangler.toml
file as TWITCH_ID=
.client_secret
:
string
.Required
.wrangler.toml
file as TWITCH_SECRET=
.
⚠️ Do not share your client secret to ensure the security of your app.
scope
:
string[]
.Required
.redirect_uri
:
string
.Required
.state
:
string
.Optional
.force_verify
:
boolean
.Optional
.true
if you want to force the user to verify their account. Defaults to false
.After the completion of the Twitch OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
twitchAuth
method provides 4 set key data:
token
:
{
token: string
expires_in: number
}
refresh-token
:
{
token: string
expires_in: number
}
granted-scopes
:
string[]
.user-twitch
:
{
id: string
login: string
display_name: string
type: string
broadcaster_type: string
description: string
profile_image_url: string
offline_image_url: string
view_count: number
email: string
created_at: string
}
[!NOTE] To access this data, utilize the
c.get
method within the callback of the upcoming HTTP request handler.
app.get('/twitch', (c) => {
const token = c.get('token')
const refreshToken = c.get('refresh-token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-twitch')
return c.json({
token,
refreshToken,
grantedScopes,
user,
})
})
Once the user token expires you can refresh their token without 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 Twitch gives you a newrefresh_token
along with the new token.
import { twitchAuth, refreshToken } from '@hono/oauth-providers/twitch'
app.post('/twitch/refresh', async (c, next) => {
const newTokens = await refreshToken(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN)
// newTokens = {
// token_type: 'bearer',
// access_token: 'new-access-token',
// expires_in: 60000,
// refresh_token: 'new-refresh-token',
// scope: ['user:read:email', 'channel:read:subscriptions', 'bits:read']
// }
// ...
})
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
and the token
to be revoked as parameters.
It returns a boolean
to tell whether the token was revoked or not.
import { twitchAuth, revokeToken } from '@hono/oauth-providers/twitch'
app.post('/remove-user', async (c, next) => {
const revoked = await revokeToken(CLIENT_ID, USER_TOKEN)
// revoked = true | false
// ...
})
You can validate a Twitch access token to verify it's still valid or to obtain information about the token, such as its expiration date, scopes, and the associated user.
You can use validateToken
method, which accepts the token
to be validated as parameter and returns TwitchValidateSuccess
if valid or throws HTTPException
upon failure.
IMPORTANT: Twitch requires applications to validate OAuth tokens when they start and on an hourly basis thereafter. Failure to validate tokens may result in Twitch taking punitive action, such as revoking API keys or throttling performance. When a token becomes invalid, your app should terminate all sessions using that token immediately. Read more
The validation endpoint helps your application detect when tokens become invalid for reasons other than expiration, such as when users disconnect your integration from their Twitch account. When a token becomes invalid, your app should terminate all sessions using that token.
For security and compliance, make sure to implement regular token validation in your application. If a token becomes invalid, promptly sign out the user and terminate their OAuth session.
import { Hono } from 'hono'
import { msentraAuth } from '@hono/oauth-providers/msentra'
const app = new Hono()
app.use(
'/msentra',
msentraAuth({
client_id: process.env.MSENTRA_ID,
client_secret: process.env.MSENTRA_SECRET,
tenant_id: process.env.MSENTRA_TENANT_ID
scope: [
'openid',
'profile',
'email',
'https;//graph.microsoft.com/.default',
]
})
)
export default app
client_id
:
string
.Required
.client_secret
:
string
.Required
.⚠️ Do not share your client secret to ensure the security of your app.
tenant_id
:
string
Required
.scope
:
string[]
.Required
.After the completion of the MSEntra OAuth flow, essential data has been prepared for use in the subsequent steps that your app needs to take.
msentraAuth
method provides 4 set key data:
token
:
{
token: string
expires_in: number
refresh_token: string
}
granted-scopes
:
string[]
.user-msentra
:
{
businessPhones: string[],
displayName: string
givenName: string
jobTitle: string
mail: string
mobilePhone: string
officeLocation: string
surname: string
userPrincipalName: string
id: string
}
[!NOTE] To access this data, utilize the
c.get
method within the callback of the upcoming HTTP request handler.
app.get('/msentra', (c) => {
const token = c.get('token')
const grantedScopes = c.get('granted-scopes')
const user = c.get('user-msentra')
return c.json({
token,
grantedScopes,
user,
})
})
Once the user token expires you can refresh their token without 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
, tenant_id
, and refresh_token
as parameters.
[!NOTE] The
refresh_token
can be used once. Once the token is refreshed MSEntra gives you a newrefresh_token
along with the new token.
import { msentraAuth, refreshToken } from '@hono/oauth-providers/msentra'
app.get('/msentra/refresh', (c, next) => {
const newTokens = await refreshToken({ client_id, client_secret, tenant_id, refresh_token })
})
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.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
}
)
monoald https://github.com/monoald
MIT
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/
):
FAQs
Social login for Hono JS, integrate authentication with facebook, github, google and linkedin to your projects.
The npm package @hono/oauth-providers receives a total of 3,670 weekly downloads. As such, @hono/oauth-providers popularity was classified as popular.
We found that @hono/oauth-providers demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The Rust Security Response WG is warning of phishing emails from rustfoundation.dev targeting crates.io users.
Product
Socket now lets you customize pull request alert headers, helping security teams share clear guidance right in PRs to speed reviews and reduce back-and-forth.
Product
Socket's Rust support is moving to Beta: all users can scan Cargo projects and generate SBOMs, including Cargo.toml-only crates, with Rust-aware supply chain checks.