remix-auth-oauth2
Advanced tools
Comparing version 3.0.0 to 3.1.0
@@ -29,4 +29,54 @@ import { type SetCookieInit } from "@mjackson/headers"; | ||
protected authorizationParams(params: URLSearchParams, request: Request): URLSearchParams; | ||
/** | ||
* Get a new OAuth2 Tokens object using the refresh token once the previous | ||
* access token has expired. | ||
* @param refreshToken The refresh token to use to get a new access token | ||
* @returns The new OAuth2 tokens object | ||
* @example | ||
* ```ts | ||
* let tokens = await strategy.refreshToken(refreshToken); | ||
* console.log(tokens.accessToken()); | ||
* ``` | ||
*/ | ||
refreshToken(refreshToken: string): Promise<OAuth2Tokens>; | ||
/** | ||
* Users the token revocation endpoint of the identity provider to revoke the | ||
* access token and make it invalid. | ||
* | ||
* @param token The access token to revoke | ||
* @example | ||
* ```ts | ||
* // Get it from where you stored it | ||
* let accessToken = await getAccessToken(); | ||
* await strategy.revokeToken(tokens.access_token); | ||
* ``` | ||
*/ | ||
revokeToken(token: string): Promise<void>; | ||
/** | ||
* Discover the OAuth2 issuer and create a new OAuth2Strategy instance from | ||
* the OIDC configuration that is returned. | ||
* | ||
* This method will fetch the OIDC configuration from the issuer and create a | ||
* new OAuth2Strategy instance with the provided options and verify function. | ||
* | ||
* @param uri The URI of the issuer, this can be a full URL or just the domain | ||
* @param options The rest of the options to pass to the OAuth2Strategy constructor, clientId, clientSecret, redirectURI, and scopes are required. | ||
* @param verify The verify function to use with the OAuth2Strategy instance | ||
* @returns A new OAuth2Strategy instance | ||
* @example | ||
* ```ts | ||
* let strategy = await OAuth2Strategy.discover( | ||
* "https://accounts.google.com", | ||
* { | ||
* clientId: "your-client-id", | ||
* clientSecret: "your-client-secret", | ||
* redirectURI: "https://your-app.com/auth/callback", | ||
* scopes: ["openid", "email", "profile"], | ||
* }, | ||
* async ({ tokens }) => { | ||
* return getUserProfile(tokens.access_token); | ||
* }, | ||
* ); | ||
*/ | ||
static discover<U>(uri: string | URL, options: Pick<OAuth2Strategy.ConstructorOptions, "clientId" | "clientSecret" | "cookie" | "redirectURI" | "scopes"> & Partial<Omit<OAuth2Strategy.ConstructorOptions, "clientId" | "clientSecret" | "cookie" | "redirectURI" | "scopes">>, verify: Strategy.VerifyFunction<U, OAuth2Strategy.VerifyOptions>): Promise<OAuth2Strategy<U>>; | ||
} | ||
@@ -33,0 +83,0 @@ export declare namespace OAuth2Strategy { |
@@ -1,2 +0,3 @@ | ||
import { Cookie, SetCookie, } from "@mjackson/headers"; | ||
import { ObjectParser } from "@edgefirst-dev/data/parser"; | ||
import { Cookie, SetCookie } from "@mjackson/headers"; | ||
import { CodeChallengeMethod, OAuth2Client, OAuth2RequestError, generateCodeVerifier, generateState, } from "arctic"; | ||
@@ -6,3 +7,4 @@ import createDebug from "debug"; | ||
import { redirect } from "./lib/redirect.js"; | ||
let debug = createDebug("OAuth2Strategy"); | ||
const debug = createDebug("OAuth2Strategy"); | ||
const WELL_KNOWN = ".well-known/openid-configuration"; | ||
export class OAuth2Strategy extends Strategy { | ||
@@ -100,5 +102,28 @@ options; | ||
} | ||
/** | ||
* Get a new OAuth2 Tokens object using the refresh token once the previous | ||
* access token has expired. | ||
* @param refreshToken The refresh token to use to get a new access token | ||
* @returns The new OAuth2 tokens object | ||
* @example | ||
* ```ts | ||
* let tokens = await strategy.refreshToken(refreshToken); | ||
* console.log(tokens.accessToken()); | ||
* ``` | ||
*/ | ||
refreshToken(refreshToken) { | ||
return this.client.refreshAccessToken(this.options.tokenEndpoint.toString(), refreshToken, this.options.scopes ?? []); | ||
} | ||
/** | ||
* Users the token revocation endpoint of the identity provider to revoke the | ||
* access token and make it invalid. | ||
* | ||
* @param token The access token to revoke | ||
* @example | ||
* ```ts | ||
* // Get it from where you stored it | ||
* let accessToken = await getAccessToken(); | ||
* await strategy.revokeToken(tokens.access_token); | ||
* ``` | ||
*/ | ||
revokeToken(token) { | ||
@@ -110,3 +135,61 @@ let endpoint = this.options.tokenRevocationEndpoint; | ||
} | ||
/** | ||
* Discover the OAuth2 issuer and create a new OAuth2Strategy instance from | ||
* the OIDC configuration that is returned. | ||
* | ||
* This method will fetch the OIDC configuration from the issuer and create a | ||
* new OAuth2Strategy instance with the provided options and verify function. | ||
* | ||
* @param uri The URI of the issuer, this can be a full URL or just the domain | ||
* @param options The rest of the options to pass to the OAuth2Strategy constructor, clientId, clientSecret, redirectURI, and scopes are required. | ||
* @param verify The verify function to use with the OAuth2Strategy instance | ||
* @returns A new OAuth2Strategy instance | ||
* @example | ||
* ```ts | ||
* let strategy = await OAuth2Strategy.discover( | ||
* "https://accounts.google.com", | ||
* { | ||
* clientId: "your-client-id", | ||
* clientSecret: "your-client-secret", | ||
* redirectURI: "https://your-app.com/auth/callback", | ||
* scopes: ["openid", "email", "profile"], | ||
* }, | ||
* async ({ tokens }) => { | ||
* return getUserProfile(tokens.access_token); | ||
* }, | ||
* ); | ||
*/ | ||
static async discover(uri, options, verify) { | ||
// Parse the URI into a URL object | ||
let url = new URL(uri); | ||
if (!url.pathname.includes("well-known")) { | ||
// Add the well-known path to the URL if it's not already there | ||
url.pathname = url.pathname.endsWith("/") | ||
? `${url.pathname}${WELL_KNOWN}` | ||
: `${url.pathname}/${WELL_KNOWN}`; | ||
} | ||
// Fetch the metadata from the issuer and validate it | ||
let response = await fetch(url, { | ||
headers: { Accept: "application/json" }, | ||
}); | ||
// If the response is not OK, throw an error | ||
if (!response.ok) | ||
throw new Error(`Failed to discover issuer at ${url}`); | ||
// Parse the response body | ||
let parser = new ObjectParser(await response.json()); | ||
return new OAuth2Strategy({ | ||
authorizationEndpoint: new URL(parser.string("authorization_endpoint")), | ||
tokenEndpoint: new URL(parser.string("token_endpoint")), | ||
tokenRevocationEndpoint: parser.has("revocation_endpoint") | ||
? new URL(parser.string("revocation_endpoint")) | ||
: undefined, | ||
codeChallengeMethod: parser.has("code_challenge_methods_supported") | ||
? parser.array("code_challenge_methods_supported").includes("S256") | ||
? CodeChallengeMethod.S256 | ||
: CodeChallengeMethod.Plain | ||
: undefined, | ||
...options, | ||
}, verify); | ||
} | ||
} | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "remix-auth-oauth2", | ||
"version": "3.0.0", | ||
"version": "3.1.0", | ||
"description": "A strategy to use and implement OAuth2 framework for authentication with federated services like Google, Facebook, GitHub, etc.", | ||
@@ -51,2 +51,3 @@ "license": "MIT", | ||
"dependencies": { | ||
"@edgefirst-dev/data": "^0.0.2", | ||
"@mjackson/headers": "^0.8.0", | ||
@@ -53,0 +54,0 @@ "@oslojs/crypto": "^1.0.1", |
@@ -5,2 +5,5 @@ # OAuth2Strategy | ||
> [!WARN] | ||
> This strategy expects the identity provider to strictly follow the OAuth2 specification. If the provider does not follow the specification and diverges from it, this strategy may not work as expected. | ||
## Supported runtimes | ||
@@ -58,2 +61,31 @@ | ||
Then you will need to setup your routes, for the OAuth2 flows you will need to call the `authenticate` method twice. | ||
First, you will call the `authenticate` method with the provider name you set in the authenticator. | ||
```ts | ||
export async function action({ request }: Route.ActionArgs) { | ||
await authenticator.authenticate("provider-name", { request }); | ||
} | ||
``` | ||
> [!NOTE] | ||
> This route can be an `action` or a `loader`, it depends if you trigger the flow doing a POST or GET request. | ||
This will start the OAuth2 flow and redirect the user to the provider's login page. Once the user logs in and authorizes your application, the provider will redirect the user back to your application redirect URI. | ||
You will now need a route on that URI to handle the callback from the provider. | ||
```ts | ||
export async function loader({ request }: Route.LoaderArgs) { | ||
let user = await authenticator.authenticate("provider-name", { request }); | ||
// now you have the user object with the data you returned in the verify function | ||
} | ||
``` | ||
> [!NOTE] | ||
> This route must be a `loader` as the redirect will trigger a `GET` request. | ||
Once you have the `user` object returned by your strategy verify function, you can do whatever you want with that information. This can be storing the user in a session, creating a new user in your database, link the account to an existing user in your database, etc. | ||
### Using the Refresh Token | ||
@@ -91,3 +123,3 @@ | ||
### Revoking tokens | ||
### Revoking Tokens | ||
@@ -99,1 +131,32 @@ You can revoke the access token the user has with the provider. | ||
``` | ||
### Discovering the Provider | ||
If you want to discover the provider's endpoints, you can use the `discover` static method. | ||
```ts | ||
export let authenticator = new Authenticator<User>(); | ||
authenticator.use( | ||
await OAuth2Strategy.discover<User>( | ||
"https://provider.com", | ||
{ | ||
clientId: CLIENT_ID, | ||
clientSecret: CLIENT_SECRET, | ||
redirectURI: "https://example.app/auth/callback", | ||
scopes: ["openid", "email", "profile"], // optional | ||
}, | ||
async ({ tokens, request }) => { | ||
// here you can use the params above to get the user and return it | ||
// what you do inside this and how you find the user is up to you | ||
return await getUser(tokens, request); | ||
} | ||
) | ||
); | ||
``` | ||
This will fetch the provider's configuration endpoint (`/.well-known/openid-configuration`) and grab the authorization, token and revocation endpoints from it, it will also grab the code challenge method supported and try to use S256 if it is supported. | ||
Remember this will do a fetch when then strategy is created, this will add a latency to the startup of your application. | ||
It's recommended to use this method only once and then copy the endpoints to your configuration. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
31287
369
159
8
+ Added@edgefirst-dev/data@^0.0.2
+ Added@edgefirst-dev/data@0.0.2(transitive)