Skip to content

Commit

Permalink
Implement initial strategies
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-vd committed Dec 6, 2024
1 parent 29a839b commit 1b626bd
Show file tree
Hide file tree
Showing 15 changed files with 830 additions and 123 deletions.
22 changes: 0 additions & 22 deletions .eslintrc.js

This file was deleted.

2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github: sergiodxa
github: paul-vd
8 changes: 4 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ updates:
schedule:
interval: "monthly"
reviewers:
- "sergiodxa"
- "paul-vd"
assignees:
- "sergiodxa"
- "paul-vd"

- package-ecosystem: npm
directory: /
schedule:
interval: "weekly"
reviewers:
- "sergiodxa"
- "paul-vd"
assignees:
- "sergiodxa"
- "paul-vd"
4 changes: 2 additions & 2 deletions .github/workflows/bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ jobs:
node-version: "lts/*"

- run: |
git config user.name 'Sergio Xalambrí'
git config user.email 'hello@sergiodxa.com'
git config user.name 'Paul van Dyk'
git config user.email 'paul@vandyk.fr'
- run: npm version ${{ github.event.inputs.version }}
- run: bun run quality:fix
Expand Down
102 changes: 72 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,90 @@
# Remix Auth - Strategy Template
# Remix Auth - Strategies

> A template for creating a new Remix Auth strategy.
> A collection of authentication strategies for Remix Auth, designed to simplify the integration of various OAuth2 providers.
If you want to create a new strategy for Remix Auth, you could use this as a template for your repository.
This repository provides a set of pre-built strategies for use with Remix Auth, allowing developers to easily integrate authentication with popular OAuth2 providers like Azure AD B2C and Auth0. Each strategy is designed to be flexible and customizable, supporting a wide range of use cases and configurations.

The repo installs the latest version of Remix Auth and do the setup for you to have tests, linting and typechecking.
## Features

## How to use it
- **Multiple Strategies**: Includes strategies for Azure AD B2C and Auth0, with more to come.
- **Customizable**: Easily configure scopes, prompts, and other OAuth2 parameters.
- **TypeScript Support**: Fully typed for a better development experience.
- **Tested**: Each strategy comes with a comprehensive suite of tests to ensure reliability.

1. In the `package.json` change `name` to your strategy name, also add a description and ideally an author, repository and homepage keys.
2. In `src/index.ts` change the `MyStrategy` for the strategy name you want to use.
3. Implement the strategy flow inside the `authenticate` method. Use `this.success` and `this.failure` to correctly send finish the flow.
4. In `tests/index.test.ts` change the tests to use your strategy and test it. Inside the tests you have access to `jest-fetch-mock` to mock any fetch you may need to do.
5. Once you are ready, set the secrets on Github
- `NPM_TOKEN`: The token for the npm registry
- `GIT_USER_NAME`: The git username you want the bump workflow to use in the commit.
- `GIT_USER_EMAIL`: The email you want the bump workflow to use in the commit.
## Installation

## Scripts
Install the package using your preferred package manager:

- `build`: Build the project for production using the TypeScript compiler (strips the types).
- `typecheck`: Check the project for type errors, this also happens in build but it's useful to do in development.
- `lint`: Runs ESLint against the source codebase to ensure it pass the linting rules.
- `test`: Runs all the test using Jest.

## Documentations

To facilitate creating a documentation for your strategy, you can use the following Markdown
```bash
npm install @vd-tech/remix-auth-strategies
# or
yarn add @vd-tech/remix-auth-strategies
```

```markdown
# Strategy Name
## Usage

### Azure AD B2C Strategy

```typescript
import { AzureB2CStrategy } from "@vd-tech/remix-auth-strategies";

const azureStrategy = new AzureB2CStrategy(
{
domain: "your-tenant.b2clogin.com",
tenant: "your-tenant",
policy: "B2C_1_signin",
clientId: "your-client-id",
clientSecret: "your-client-secret",
redirectURI: "http://localhost:3000/auth/callback",
scopes: ["openid", "profile", "offline_access"],
prompt: "login",
},
async ({ profile }) => {
// Handle user verification here
return { id: profile.id, email: profile.emails[0].value };
}
);
```

<!-- Description -->
### Auth0 Strategy

```typescript
import { Auth0Strategy } from "@vd-tech/remix-auth-strategies";

const auth0Strategy = new Auth0Strategy(
{
domain: "your-domain.auth0.com",
clientId: "your-client-id",
clientSecret: "your-client-secret",
redirectURI: "http://localhost:3000/auth/callback",
scopes: ["openid", "profile", "email"],
},
async ({ profile }) => {
// Handle user verification here
return { id: profile.id, email: profile.emails[0].value };
}
);
```

## Supported runtimes
## Supported Runtimes

| Runtime | Has Support |
| ---------- | ----------- |
| Node.js ||
| Cloudflare ||

<!-- If it doesn't support one runtime, explain here why -->
## Contributing

## How to use
Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project.

<!-- Explain how to use the strategy, here you should tell what options it expects from the developer when instantiating the strategy -->
```
## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Maintainers

- [Paul van Dyk](https://github.com/paul-vd)

## Acknowledgments

Special thanks to the Remix Auth community for their support and contributions.
Binary file modified bun.lockb
Binary file not shown.
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "remix-auth-strategy-template",
"name": "@vd-tech/remix-auth-strategies",
"version": "0.0.0",
"description": "A description of the strategy",
"description": "A collection of authentication strategies for Remix Auth, designed to simplify the integration of various OAuth2 providers.",
"license": "MIT",
"funding": ["https://github.com/sponsors/sergiodxa"],
"author": {
Expand All @@ -11,11 +11,11 @@
},
"repository": {
"type": "git",
"url": "https://github.com/sergiodxa/remix-auth-strategy-template"
"url": "https://github.com/vd-tech/remix-auth-strategies"
},
"homepage": "https://sergiodxa.github.io/remix-auth-strategy-template",
"homepage": "https://github.com/vd-tech/remix-auth-strategies",
"bugs": {
"url": "https://github.com/sergiodxa/remix-auth-strategy-template/issues"
"url": "https://github.com/vd-tech/remix-auth-strategies/issues"
},
"keywords": ["remix", "remix-auth", "auth", "authentication", "strategy"],
"scripts": {
Expand All @@ -32,12 +32,13 @@
},
"files": ["build", "package.json", "README.md"],
"exports": {
".": "./build/index.js",
"./azure": "./build/azure.js",
"./auth0": "./build/auth0.js",
"./package.json": "./package.json"
},
"dependencies": {
"react-router": "^7.0.2",
"remix-auth": "^4.0.0"
"remix-auth": "^4.0.0",
"remix-auth-oauth2": "^3.1.0"
},
"peerDependencies": {},
"devDependencies": {
Expand All @@ -46,6 +47,7 @@
"@total-typescript/tsconfig": "^1.0.4",
"@types/bun": "^1.1.6",
"consola": "^3.2.3",
"msw": "^2.6.7",
"typedoc": "^0.26.5",
"typedoc-plugin-mdn-links": "^3.2.6",
"typescript": "^5.5.4"
Expand Down
89 changes: 89 additions & 0 deletions src/auth0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { OAuth2Strategy } from "remix-auth-oauth2";
import type { Strategy } from "remix-auth/strategy";

/**
* @see https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims
*/
type Auth0Scopes = Array<(string & {}) | "email" | "openid" | "profile">;

export interface Auth0StrategyOptions
extends Pick<
OAuth2Strategy.ConstructorOptions,
"clientId" | "clientSecret" | "redirectURI" | "scopes"
> {
/**
* OAuth2 strategy options
*/
scopes?: Auth0Scopes;

/**
* Auth0 strategy options
*/
domain: string;
audience?: string;
organization?: string;
invitation?: string;
connection?: string;
}

export const Auth0StrategyDefaultName = "auth0";

export class Auth0Strategy<User> extends OAuth2Strategy<User> {
public override name = Auth0StrategyDefaultName;

private scopes: Auth0Scopes;
private readonly audience?: string;
private readonly organization?: string;
private readonly invitation?: string;
private readonly connection?: string;

public constructor(
options: Auth0StrategyOptions,
verify: Strategy.VerifyFunction<User, OAuth2Strategy.VerifyOptions>,
) {
super(
{
authorizationEndpoint: `https://${options.domain}/authorize`,
tokenEndpoint: `https://${options.domain}/oauth/token`,
tokenRevocationEndpoint: `https://${options.domain}/oauth/revoke`,
clientId: options.clientId,
clientSecret: options.clientSecret,
redirectURI: options.redirectURI,
},
verify,
);

this.scopes = options.scopes ?? ["openid", "profile", "email"];
this.audience = options.audience;
this.organization = options.organization;
this.invitation = options.invitation;
this.connection = options.connection;
}

protected override authorizationParams(
params: URLSearchParams,
request: Request,
) {
params.set("scope", this.scopes.join(" "));
if (this.audience) {
params.set("audience", this.audience);
}
if (this.organization) {
params.set("organization", this.organization);
}
if (this.invitation) {
params.set("invitation", this.invitation);
}
if (this.connection) {
params.set("connection", this.connection);
}

// Add additional search params
const requestSearchParams = new URL(request.url).searchParams;
for (const [key, value] of requestSearchParams) {
params.set(key, value);
}

return params;
}
}
66 changes: 66 additions & 0 deletions src/azure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { OAuth2Strategy } from "remix-auth-oauth2";
import type { Strategy } from "remix-auth/strategy";

/**
* @see https://learn.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview
*/
type AzureScopes = Array<
(string & {}) | "openid" | "profile" | "offline_access"
>;

export interface AzureStrategyOptions
extends Pick<
OAuth2Strategy.ConstructorOptions,
"clientId" | "clientSecret" | "redirectURI"
> {
/**
* OAuth2 strategy options
*/
scopes?: AzureScopes;

/**
* Azure B2C specific options
*/
domain: string;
tenant: string;
policy: string;
prompt?: "none" | "login" | "consent" | "select_account";
}

export const AzureStrategyDefaultName = "azure";

export class AzureStrategy<User> extends OAuth2Strategy<User> {
public override name = AzureStrategyDefaultName;

private scopes: AzureScopes;
private readonly prompt?: string;

constructor(
options: AzureStrategyOptions,
verify: Strategy.VerifyFunction<User, OAuth2Strategy.VerifyOptions>,
) {
const baseUrl = `https://${options.domain}/${options.tenant}/${options.policy}/oauth2/v2.0`;

super(
{
authorizationEndpoint: `${baseUrl}/authorize`,
tokenEndpoint: `${baseUrl}/token`,
clientId: options.clientId,
clientSecret: options.clientSecret,
redirectURI: options.redirectURI,
},
verify,
);

this.scopes = options.scopes ?? ["openid", "profile", "offline_access"];
this.prompt = options.prompt ?? "none";
}

protected override authorizationParams(params: URLSearchParams) {
params.set("scope", this.scopes.join(" "));
if (this.prompt) {
params.set("prompt", this.prompt);
}
return params;
}
}
Loading

0 comments on commit 1b626bd

Please sign in to comment.