ebox86 / discord-cognito-openid-wrapper

Small shim that allows AWS Cognito to talk to discord (by providing an OpenID wrapper around the Discord API)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Discord OpenID Connect Wrapper for Cognito

This is a modification of https://github.com/qwerqy/discord-cognito-openid-wrapper which is a fork of https://github.com/TimothyJones/github-cognito-openid-wrapper. Changes have been made to this repo to more extensively integrate Discord as a OIDC provider in this repo and all references to 'github' have been removed.

Do you want to add Discord as an OIDC (OpenID Connect) provider to an AWS Cognito User Pool? Have you run in to trouble because Discord only provides OAuth2.0 endpoints, and doesn't support OpenID Connect?

This project allows you to wrap your Discord OAuth App in an OpenID Connect layer, allowing you to use it with AWS Cognito.

Here are some questions you may immediately have:

  • Why does Cognito not support federation with OAuth? Because OAuth provides no standard way of requesting user identity data. (see the background section below for more details).

  • Why has no one written a shim to wrap general OAuth implementations with an OpenID Connect layer? Because OAuth provides no standard way of requesting user identity data, any shim must be custom written for the particular OAuth implementation that's wrapped.

  • Discord is very popular, has someone written this specific custom wrapper before? As far as I can tell, if it has been written, it has not been open sourced. Until now!

Project overview

When deployed, this project sits between Cognito and Discord:

Overview

This allows you to use Discord as an OpenID Identity Provider (IdP) for federation with a Cognito User Pool.

The project implements everything needed by the OIDC User Pool IdP authentication flow used by Cognito.

It implements the following endpoints from the OpenID Connect Core Spec:

  • Authorization - used to start the authorisation process (spec)
  • Token - used to exchange an authorisation code for an access and ID token (spec)
  • UserInfo - used to exchange an access token for information about the user (spec)
  • jwks - used to describe the keys used to sign ID tokens (implied by spec)

It also implements the following OpenID Connect Discovery endpoint:

  • Configuration - used to discover configuration of this OpenID implementation's endpoints and capabilities. (spec)

Out of the box, you can deploy it as a CloudFormation stack, or run it as a web server with node.

Getting Started

This project is intended to be deployed as a series of lambda functions alongside an API Gateway. This means it's easy to use in conjunction with Cognito, and should be cheap to host and run.

You can also deploy it as a http server running as a node app. This is useful for testing, exposing it to Cognito using something like ngrok.

1: Setup

You will need to:

  • Create a Cognito User Pool (instructions).
  • Configure App Integration for your User Pool (instructions). Note down the domain name.
    • Note: If you are using AWS Amplify cli to provision cognito user pools, the previous two steps are already done for you and you DO NOT need to do them again. You will see two app integrations already created if you have used Amplify, use the 'cient-web' app clients for the followng.
  • Create a Discord OAuth App (instructions, with the following settings:
    • Redirects: https://<Your Cognito Domain>/oauth2/idpresponse redirects
    • Note down the Client ID and secret on the General Information tab. clientsecret

Note: if you are using amplify cli to setup your userpools, the following options should be selected:

Do you want to use the default authentication and security configuration? Manual configuration

Select the authentication/authorization services that you want to use: User Sign-Up & Sign-In only (Best used with a cloud API only)

Please provide a friendly name for your resource that will be used to label this category in the project: reactfrontendd8a830ead8a830ea

Please provide a name for your user pool: reactfrontendd8a830ea_userpool_d8a830ea

Warning: you will not be able to edit these selections.

How do you want users to be able to sign in? Username

Do you want to add User Pool Groups? No

Do you want to add an admin queries API? No

Multifactor authentication (MFA) user login options: OFF

Email based user registration/forgot password: Enabled (Requires per-user email entry at registration)

Please specify an email verification subject: Your verification code

Please specify an email verification message: Your verification code is {####}

Do you want to override the default password policy for this User Pool? No

Warning: you will not be able to edit these selections.

What attributes are required for signing up? Preferred Username (This attribute is not supported by Facebook, Google, Login With Amazon.)

Specify the app's refresh token expiration period (in days): 30

Do you want to specify the user attributes this app can read and write? No

Do you want to enable any of the following capabilities?

Do you want to use an OAuth flow? Yes

What domain name prefix do you want to use? <your_prefix_here>

Enter your redirect signin URI: http://localhost:3000/

Do you want to add another redirect signin URI No

Enter your redirect signout URI: http://localhost:3000/ Do you want to add another redirect signout URI No

Select the OAuth flows enabled for this project. Authorization code grant

Select the OAuth scopes enabled for this project. OpenID, Profile

Select the social providers you want to configure for your user pool: (do not select any)

Do you want to configure Lambda Triggers for Cognito? No

Next you need to decide if you'd like to deploy with lambda/API Gateway (follow Step 2a), or as a node server (follow Step 2b)

2a: Deployment with lambda and API Gateway

  • Install the aws and sam CLIs from AWS:

  • Run aws configure and set appropriate access keys etc

  • Set environment variables for the OAuth App client/secret, callback url, stack name, etc:

     cp example-config.sh config.sh
     vim config.sh # Or whatever your favourite editor is
    
  • Run npm install and npm run deploy

  • Note down the DNS of the deployed API Gateway (available in the AWS console).

2b: Running the node server

  • Set environment variables for the OAuth App client/secret, callback url, and port to run the server on:

     cp example-config.sh config.sh
     vim config.sh # Or whatever your favourite editor is
    
  • Source the config file:

  source config.sh
  • Run npm run start to fire up an auto-refreshing development build of the server (production deployment is out of scope for this repository, but you can expose it using something like ngrok for easy development and testing with Cognito).

3: Finalise Cognito configuration

  • Configure the OIDC integration in AWS console for Cognito (described below, but following these instructions). The following settings are required:
    • Client ID: The Discord Client ID above
    • Authorize scope: openid identify email
      • Read more about discord oauth2 scopes here. This integration only supports the identify and Email scopes. Others are not supported for now. If you want to request the users email, add email to the Authorization Scope field. Also, uncomment lines 29-41 on /src/openid.js, and lines 62 and 63 from /src/discord.js
    • Issuer: either https://<Your API Gateway DNS name>/Prod (for lambda with API gateway, replace Prod with the correct stage name) or https://<your webserver>/ (for the node server).
    • If you have deployed the web app: Run discovery (big blue button next to Issuer).
    • If you have deployed the lambda/Gateway: For some reason, Cognito is unable to do OpenID Discovery. You will need to configure the endpoints manually. They are:
      • Authorization endpoint: https://<Your API Gateway DNS name>/Prod/authorize
      • Token endpoint: https://<Your API Gateway DNS name>/Prod/token
      • Userinfo endpoint: https://<Your API Gateway DNS name>/Prod/userinfo
      • JWKS uri: https://<Your API Gateway DNS name>/Prod/.well-known/jwks.json
  • Configure the Attribute Mapping in the AWS console:

Attribute mapping

  • Ensure that your new provider is enabled under Enabled Identity Providers on the App Client Settings screen under App Integration.

That's it! If you need to redeploy the lambda/API gateway solution, all you need to do is run npm run deploy again.

The details

Background

There are two important concepts for identity federation:

  • Authentication: Is this user who they say they are?
  • Authorisation: Is the user allowed to use a particular resource?

OAuth

OAuth2.0 is an authorisation framework, used for determining whether a user is allowed to access a resource (like private user profile data). In order to do this, it's usually necessary for authentication of the user to happen before authorisation.

This means that most OAuth2.0 implementations (including Discord) include authentication in a step of the authorisation process. For all practical purposes, most OAuth2.0 implementations (including Discord)can be thought of as providing both authorisation and authentication.

Below is a diagram of the authentication code flow for OAuth:

OAuth flow

(The solid lines are http requests from the browser, and then dashed lines are back-channel requests).

As you can see in the diagram, a drawback of OAuth is that it provides no standard way of finding out user data such as name, avatar picture, email address(es), etc. This is one of the problems that is solved by OpenID.

OpenID Connect

To provide a standard way of learning about users, OpenID Connect is an identity layer built on top of OAuth2.0. It extends the token endpoint from OAuth to include an ID Token alongside the access token, and provides a userinfo endpoint, where information describing the authenticated user can be accessed.

OpenID Connect

OpenID Connect describes a standard way to get user data, and is therefore a good choice for identity federation.

A custom shim for Discord

This project provides the OpenID shim to wrap Discord's OAuth implementation, by combining the two diagrams:

Discord Shim

The userinfo request is handled by Discord API request: /users/@me .

You can compare this workflow to the documented Cognito workflow here

Code layout

├── scripts             # Bash scripts for deployment and key generation
├── src                 # Source code
│    ├── __mocks__      # Mock private key data for tests
│    └── connectors     # Common code for both lambda and web handlers
│         ├── lambda    # AWS lambda handlers
│         │    └── util # Helper functions for lambdas
│         └── web       # Express.js webserver (useful for local deployment)
├── docs                # Documentation images
├── config              # Configuration for tests
├── dist-web            # Dist folder for web server deployment
└-- dist-lambda         # Dist folder for lambda deployment

npm targets

  • build and build-dist: create packages in the dist-lambda folder (for the lambda deployment) and the dist-web folder (for the node web server).
  • test: Run unit tests with Jest
  • lint: Run eslint to check code style
  • test-dev: Run unit tests continuously, watching the file system for changes (useful for development)
  • deploy: This script builds the project, then creates and deploys the cloudformation stack with the API gateway and the endpoints as lambdas

Scripts

  • scripts/create-key.sh: If the private key is missing, generate a new one. This is run as a preinstall script before npm install
  • scripts/deploy.sh: This is the deploy part of npm run deploy. It uploads the dist folder to S3, and then creates the cloudformation stack that contains
    the API gateway and lambdas

Tests

Note Tests are currently not working and are commented out - these tests were provided as part of the original implementation and have not yet been converted over to test against Discord's apis.

Tests are provided with Jest using chai's expect, included by a shim based on this blog post.

Pact consumer tests for the Discord API connection are provided in src/discord.pact.test.js. There is currently no provider validation performed.

Private key

The private key used to make ID tokens is stored in ./jwtRS256.key once scripts/create-key.sh is run (either manually, or as part of npm install). You may optionally replace it with your own key - if you do this, you will need to redeploy.

Missing features

This is a near-complete implementation of OpenID Connect Core. However, since the focus was on enabling Cognito's authentication flow, you may run in to some missing features if you wish to use it with a different client.

Missing Connect Core Features:

  • Private key rotation (spec)
  • Refresh tokens (spec)
  • Passing request parameters as JWTs (spec)

If you don't know what these things are, you are probably ok to use this project.

Missing non-core features:

A full OpenID implementation would also include:

Known issues

none

Extending

This section contains pointers if you would like to extend this shim.

Using other OAuth providers

If you want to use a provider other than Discord, you'll need to change the contents of userinfo in src/openid.js.

Including additional user information

If you want to include custom claims based on other Discord data (user flags, communities, etc), you can extend userinfo in src/openid.js. You may need to add extra API client calls in src/discord.js

Contributing

Contributions are welcome, especially for the missing features! Pull requests and issues are very welcome.

FAQ

How do I use this to implement Cognito logins in my app?

Login requests from your app go directly to Cognito, rather than this shim. This is because the shim sits only between Cognito and Discord, not between your app and Discord. See the Cognito app integration instructions for more details.

Can I use this shim to connect to Discord directly from another OpenID client?

Yes. This implementation isn't complete, as it focusses exclusively on Cognito's requirements. However, it does follow the OpenID spec, and is complete enough to be able to use it as an OpenID connect provider. See the missing features section above for one or two caveats.

How do I contact you to tell you that I built something cool with this code?

If you build anything cool, ping me @JonesTim on twitter (or open an issue if you have any problems).

License

BSD 3-Clause License

About

Small shim that allows AWS Cognito to talk to discord (by providing an OpenID wrapper around the Discord API)

License:BSD 3-Clause "New" or "Revised" License


Languages

Language:JavaScript 94.8%Language:Shell 5.2%