Coder Social home page Coder Social logo

deno-oauth2-client's Introduction

OAuth2 Client for Deno

Tests deno doc

Minimalistic OAuth 2.0 client for Deno. Inspired by js-client-oauth2.

This module tries not to make assumptions on your use-cases. As such, it

  • has no external dependencies outside of Deno's standard library
  • can be used with Deno's http module or any other library for handling http requests, like oak
  • only implements OAuth 2.0 grants, letting you take care of storing and retrieving sessions, managing state parameters, etc.

Currently supported OAuth 2.0 grants:

Usage

GitHub API example using oak

import { Application, Router } from "https://deno.land/x/[email protected]/mod.ts";
import { Session } from "https://deno.land/x/[email protected]/mod.ts";
import { OAuth2Client } from "https://deno.land/x/oauth2_client/mod.ts";

const oauth2Client = new OAuth2Client({
  clientId: Deno.env.get("CLIENT_ID")!,
  clientSecret: Deno.env.get("CLIENT_SECRET")!,
  authorizationEndpointUri: "https://github.com/login/oauth/authorize",
  tokenUri: "https://github.com/login/oauth/access_token",
  redirectUri: "http://localhost:8000/oauth2/callback",
  defaults: {
    scope: "read:user",
  },
});

type AppState = {
  session: Session;
};

const router = new Router<AppState>();
router.get("/login", async (ctx) => {
  // Construct the URL for the authorization redirect and get a PKCE codeVerifier
  const { uri, codeVerifier } = await oauth2Client.code.getAuthorizationUri();

  // Store both the state and codeVerifier in the user session
  ctx.state.session.flash("codeVerifier", codeVerifier);

  // Redirect the user to the authorization endpoint
  ctx.response.redirect(uri);
});
router.get("/oauth2/callback", async (ctx) => {
  // Make sure the codeVerifier is present for the user's session
  const codeVerifier = ctx.state.session.get("codeVerifier");
  if (typeof codeVerifier !== "string") {
    throw new Error("invalid codeVerifier");
  }

  // Exchange the authorization code for an access token
  const tokens = await oauth2Client.code.getToken(ctx.request.url, {
    codeVerifier,
  });

  // Use the access token to make an authenticated API request
  const userResponse = await fetch("https://api.github.com/user", {
    headers: {
      Authorization: `Bearer ${tokens.accessToken}`,
    },
  });
  const { login } = await userResponse.json();

  ctx.response.body = `Hello, ${login}!`;
});

const app = new Application<AppState>();
app.use(Session.initMiddleware());
app.use(router.allowedMethods(), router.routes());

await app.listen({ port: 8000 });

More Examples

For more examples, check out the examples directory.

Migration

v0.*.* -> v1.*.*

With v1.0.0:

  • we introduced PKCE by default for the Authorization Code Grant
  • enabled stateValidator callbacks to return a Promise, to allow for e.g. accessing a database
  • cleaned up interface names to prevent name clashes between e.g. the AuthorizationCodeGrant and ImplicitGrant option objects.

AuthorizationCodeGrant

  • The GetUriOptions interface was renamed to AuthorizationUriOptions
  • getAuthorizationUri(...) now always returns a Promise<{ uri: URL }> instead of a plain URL.
    • when using PKCE (which is now the default), getAuthorizationUri(...) returns an object containing both an URI and the codeVerifier that you'll have to pass to the getToken(...) call inside the OAuth 2.0 redirection URI handler. Check out the examples on how to achieve that by using session cookies.
    • while you should always use PKCE if possible, there are still OAuth 2.0 servers that don't support it. To opt out of PKCE, pass { disablePkce: true } to getAuthorizationUri.

ClientCredentialsGrant

  • The GetClientCredentialsTokenOptions interface was renamed to ClientCredentialsTokenOptions

ImplicitGrant

  • The GetUriOptions interface was renamed to ImplicitUriOptions
  • The GetTokenOptions interface was renamed to ImplicitTokenOptions

ResourceOwnerPasswordCredentialsGrant

  • The GetROPCTokenOptions interface was renamed to ResourceOwnerPasswordCredentialsTokenOptions

RefreshTokenGrant

  • No changes necessary

deno-oauth2-client's People

Contributors

alexanderschau avatar cmd-johnson avatar iuioiua avatar zifeo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

deno-oauth2-client's Issues

Add Proper Comments to all public Methods

A module with 100% test coverage isn't worth much if nobody understands how to actually use it.

Add Doc-Comments to all public methods and classes so people can understand how the module is used outside the basic examples requested in #6.

Add support for Cheetah framework

I have an issue open on their page already, but I wanted to to add here too as I am trying to figure how what may be causing the callback to fail. seems that its when the getToken method is called (authorization_code_grant.ts#L135).

The author states that the context.req.raw object is a Request object so it should work but having issues

suggestion: remove `OAuth2ClientConfig.defaults`

Currently, the OAuth2ClientConfig interface has a defaults property containing requestOptions, scope and stateValidator.

I think the interface would be a little easier to work with if these properties didn't lie within the defaults property and instead just lived alongside the other properties in the interface, top-level. E.g. having these flat would allow us to use Required<OAuth2ClientConfig, "scope" | "redirectUri">.

If this were done, usability or understandability would not be lost. I'd be happy to submit a PR.

Related denoland/deno_kv_oauth#193

Examples broken - the JSON entry 'name' changed to 'login'

The JSON entry 'name' has changed to 'login'. This breaks the examples and nothing is shown in the login page. After changing it to 'login' the GitHub user name is shown.

https://github.com/cmd-johnson/deno-oauth2-client/blame/master/README.md#L54

https://github.com/cmd-johnson/deno-oauth2-client/blame/master/examples/oak.ts#L31

https://github.com/cmd-johnson/deno-oauth2-client/blob/master/examples/http.ts#L49

Here a working example for deno.dev hosting: https://dash.deno.com/projects/dry-pigeon-88

Fail with better error message when the access token cannot be retrieved

I had some of our tokens misconfigured and GitHub was answering 200 from https://github.com/login/oauth/access_token with the following body:

{                                                                                                                                             
  error: "incorrect_client_credentials",                                                                                                      
  error_description: "The client_id and/or client_secret passed are incorrect.",                                                              
  error_uri: "https://docs.github.com/apps/managing-oauth-apps/troubleshooting-oauth-app-access-token-request-erro"... 32 more characters     
} 

Instead, I received Invalid token response: missing access_token and it took me a while to debug as I had to inject the library to find the root issue. Indeed GitHub implementation is far from ideal, yet I believe this corner case should become more visible when it happens (e.g. showing the body without sensitive fields or checking whether there is an error field).

Add some usage examples

Examples would help people in learning how to use the module.

For now, at least add the basic usage using Deno's built in http module and oak.

Using this with Auth0

I am looking to use this alongside auth0 and I wanted to know how I would go about including the audience when crafting the url? I can see that there are additional options here

// extracted from my application
    return new OAuth2Client({
        defaults: {
            requestOptions: {
                urlParams: {
                    audience: Deno.env.get("AUTH0_AUDIENCE")!,
                },
            },

However this doesn't seem to be used within the code.getAuthorizationUri method and wondering what is the recommended way of setting this up. The audience is required to get a valid JWT back from their services (without the audience the payload is missing)

OAuth2Strategy requires a clientID option

I am unsur3 why I am getting the following error. This is occuring in the callback. The clientId is being set hardcoded and passes the initial signin so unsure why. I am using Auth0

[uncaught application error]: Error - OAuth2Strategy requires a clientID option

request: {
  url: "http://localhost:8000/callback?error=server_error&error_description=OAuth2Strategy%20requires%20a%20"... 17 more characters,
  method: "GET",
  hasBody: false
}
response: { status: 404, type: undefined, hasBody: false, writable: true }

    at Function.fromURLSearchParams (https://deno.land/x/[email protected]/src/errors.ts:55:12)
    at AuthorizationCodeGrant.validateAuthorizationResponse (https://deno.land/x/[email protected]/src/authorization_code_grant.ts:181:33)
    at AuthorizationCodeGrant.getToken (https://deno.land/x/[email protected]/src/authorization_code_grant.ts:139:34)
    at file:///home/adoublef/github.com/adoublef-js/oak-auth0/main.ts:42:44
    at dispatch (https://deno.land/x/[email protected]/middleware.ts:41:13)
    at https://deno.land/x/[email protected]/router.ts:1232:20
    at dispatch (https://deno.land/x/[email protected]/middleware.ts:41:13)
    at composedMiddleware (https://deno.land/x/[email protected]/middleware.ts:44:12)
    at dispatch (https://deno.land/x/[email protected]/router.ts:1238:22)
    at dispatch (https://deno.land/x/[email protected]/middleware.ts:41:13)

suggestion: `getUserInfo()`

Or, more specifically, something like OAuth2Client.getUserInfo(accessToken: string): Promise<unknown>. This would require adding a userInfoUri?: string property to OAuth2ClientConfig. I'd be happy to submit a PR.

P.S. We use this module for the official Deno KV OAuth module. Thank you for this great module. If you have any ideas on improving this module (deno-oauth2-client) or ours (deno_kv_oauth), I'd be happy to help!

Token expires_in returned from auth server as string

Working with a NetDocuments OAuth server it returns the client credentials token with expires_in formatted as a string and not a number.

Would it be possible to modify grant_base.ts parseTokenResponse method to support expires_in as both number and string and convert to number if needed prior to assignment to tokens.expires_in?

Example Concept...

if (
  ! ['number', 'string'].includes(typeof body.expires_in)
) {
  throw new TokenResponseError(
    "expires_in is not a string or number",
    response,
  );
}

....

if (body.expires_in) {
  if (typeof body.expires_in == 'string') {
    body.expires_in = parseInt(body.expires_in);
  }
  tokens.expiresIn = body.expires_in;
}

Deno.HttpClient

Hello. I just wanted to post an update regarding the Deno.HttpClient. Is it possible you can use this instead of OAK?

It would be nice to have a 100% deno solution.

Scope is not always a string

When trying to work with Linear OAuth I found this check. Linear returns a list back, not a string and thus the parse fails.

I'm not clear on need to check for scope at this stage, but maybe consider supporting a list of strings?

suggestion: Move OAuth2ClientConfig interface into src/types.ts

To allow a consuming project to utilitize the config type without pulling in the full dependency of the OAuth2Client.

This could be completely backwards compatible by re-exporting the type from the oauth2_client.ts

Happy to submit PR if you are open to this.

id_token support (OpenID)

Is there a particular reason for this library not to return the id_token when present aside the access and refresh token?

question: how to handle expired refresh token?

When using oauth2Client.refreshToken.refresh(), how do I handle when the refresh token has expired? AFAIK an error is thrown, but I'm not sure which error. My thoughts are it'd be handled like this:

try {
  await oauth2Client.refreshToken.refresh(refreshToken);
} catch (error) {
  if (isRefreshTokenExpired(error) {
    return null;
  }
  throw error;
}

Add Tests

Add tests for all included classes and their methods.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.