OAuth2Strategy
A strategy to use and implement OAuth2 framework for authentication with federated services like Google, Facebook, GitHub, etc.
Supported runtimes
Runtime | Has Support |
---|
Node.js | ✅ |
Cloudflare | ✅ |
How to use
Installation
npm add remix-auth-oauth2
Directly
You can use this strategy by adding it to your authenticator instance and configuring the correct endpoints.
export let authenticator = new Authenticator<User>(sessionStorage);
authenticator.use(
new OAuth2Strategy(
{
authorizationURL: "https://provider.com/oauth2/authorize",
tokenURL: "https://provider.com/oauth2/token",
clientID: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
callbackURL: "https://example.app/auth/callback",
useBasicAuthenticationHeader: false,
},
async ({
accessToken,
refreshToken,
extraParams,
profile,
context,
request,
}) => {
return await getUser(
accessToken,
refreshToken,
extraParams,
profile,
context,
request
);
}
),
"provider-name"
);
Extending it
You can use this strategy as a base class for another strategy using the OAuth2 framework. That way, you wouldn't need to implement the whole OAuth2 flow yourself.
The OAuth2Strategy
will handle the whole flow for you and let you replace parts of it where you need.
Let's see how the Auth0Strategy
is implemented using the OAuth2Strategy
as a base.
import type { StrategyVerifyCallback } from "remix-auth";
import type {
OAuth2Profile,
OAuth2StrategyVerifyParams,
} from "remix-auth-oauth2";
import { OAuth2Strategy } from "remix-auth-oauth2";
export interface Auth0StrategyOptions {
domain: string;
clientID: string;
clientSecret: string;
callbackURL: string;
}
export interface Auth0ExtraParams extends Record<string, string | number> {
id_token: string;
scope: string;
expires_in: 86_400;
token_type: "Bearer";
}
export interface Auth0Profile extends OAuth2Profile {
id: string;
displayName: string;
name: {
familyName: string;
givenName: string;
middleName: string;
};
emails: Array<{ value: string }>;
photos: Array<{ value: string }>;
_json: {
sub: string;
name: string;
given_name: string;
family_name: string;
middle_name: string;
nickname: string;
preferred_username: string;
profile: string;
picture: string;
website: string;
email: string;
email_verified: boolean;
gender: string;
birthdate: string;
zoneinfo: string;
locale: string;
phone_number: string;
phone_number_verified: boolean;
address: {
country: string;
};
updated_at: string;
};
}
export class Auth0Strategy<User> extends OAuth2Strategy<
User,
Auth0Profile,
Auth0ExtraParams
> {
name = "auth0";
private userInfoURL: string;
constructor(
options: Auth0StrategyOptions,
verify: StrategyVerifyCallback<
User,
OAuth2StrategyVerifyParams<Auth0Profile, Auth0ExtraParams>
>
) {
super(
{
authorizationURL: `https://${options.domain}/authorize`,
tokenURL: `https://${options.domain}/oauth/token`,
clientID: options.clientID,
clientSecret: options.clientSecret,
callbackURL: options.callbackURL,
},
verify
);
this.userInfoURL = `https://${options.domain}/userinfo`;
this.scope = options.scope || "openid profile email";
this.audience = options.audience;
}
protected authorizationParams() {
const urlSearchParams: Record<string, string> = {
scope: this.scope,
};
if (this.audience) {
urlSearchParams.audience = this.audience;
}
return new URLSearchParams(urlSearchParams);
}
protected async userProfile(accessToken: string): Promise<Auth0Profile> {
let response = await fetch(this.userInfoURL, {
headers: { Authorization: `Bearer ${accessToken}` },
});
let data: Auth0Profile["_json"] = await response.json();
let profile: Auth0Profile = {
provider: "auth0",
displayName: data.name,
id: data.sub,
name: {
familyName: data.family_name,
givenName: data.given_name,
middleName: data.middle_name,
},
emails: [{ value: data.email }],
photos: [{ value: data.picture }],
_json: data,
};
return profile;
}
}
And that's it, thanks to the OAuth2Strategy
we don't need to implement the whole OAuth2 flow ourselves and can focus on the unique parts of our strategy which is the user profile and extra params our provider may require us to send.