This application serves as an authentication proxy for the NSS Foundations Course web application that needs to authenticate users with GitHub. It solves a critical security problem: client-side applications (like SPAs or static sites) cannot securely store OAuth client secrets, but these secrets are required to complete the OAuth flow with GitHub.
Key features:
- Securely handles the exchange of OAuth authorization codes for access tokens
- Provides CORS protection to ensure only authorized origins can make requests
- Runs behind Nginx for SSL termination and secure HTTPS connections
- Dockerized for easy deployment and scaling
When a user wants to authenticate with GitHub in your web application:
- Your client app redirects the user to GitHub's OAuth authorization page
- After the user authorizes your app, GitHub redirects back to your app with an authorization code
- Your client app sends this code to this proxy server
- The proxy server (which has the client secret) exchanges the code for an access token with GitHub
- The proxy returns the access token to your client app
- Your client app can now use this token to make authenticated API requests to GitHub
sequenceDiagram
participant User
participant ClientApp as Client Application
participant Proxy as OAuth Proxy Server
participant GitHub
User->>ClientApp: Clicks "Login with GitHub"
ClientApp->>GitHub: Redirects to GitHub OAuth page<br/>(with client_id & redirect_uri)
GitHub->>User: Displays authorization prompt
User->>GitHub: Approves authorization
GitHub->>ClientApp: Redirects back with authorization code
ClientApp->>Proxy: POST /oauth/github/token<br/>(sends code & redirect_uri)
Proxy->>GitHub: POST to /login/oauth/access_token<br/>(sends code, client_id, client_secret)
GitHub->>Proxy: Returns access token
Proxy->>ClientApp: Returns access token
ClientApp->>ClientApp: Stores token, updates UI as authorized
ClientApp->>GitHub: Makes API requests with access token
GitHub->>ClientApp: Returns requested data
The Dockerfile
defines how the application is containerized. Here's a breakdown of what it does:
# Stage 1: Build Node.js application
FROM node:18-alpine AS nodejs-build
This line starts with a lightweight Alpine Linux image that has Node.js 18 installed. The AS nodejs-build
creates a named build stage that we can reference later.
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
These lines:
- Set the working directory to
/app
- Copy the package.json and package-lock.json files
- Install only the production dependencies (no dev dependencies)
- Copy all other application files
# Stage 2: Final image with Nginx and Node.js
FROM nginx:alpine
This starts a new stage using the Nginx Alpine image as the base.
RUN apk add --no-cache nodejs npm openssl
This installs Node.js, npm, and OpenSSL into the Nginx image.
WORKDIR /app
COPY --from=nodejs-build /app /app
These lines:
- Set the working directory to
/app
- Copy the built application from the previous stage
COPY nginx.conf /etc/nginx/conf.d/default.conf
This copies your Nginx configuration to where Nginx looks for site configurations.
RUN mkdir -p /etc/nginx/ssl
COPY start.sh /start.sh
RUN chmod +x /start.sh
These lines:
- Create a directory for SSL certificates
- Copy the startup script
- Make the startup script executable
EXPOSE 80 443
CMD ["/start.sh"]
These lines:
- Inform Docker that the container will use ports 80 (HTTP) and 443 (HTTPS)
- Set the startup script as the default command when the container starts
The .github/workflows/auth-proxy.yml
file defines the CI/CD pipeline that automatically tests, builds, and deploys your application when changes are pushed to the main branch.
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
continue-on-error: true
This job:
- Runs on an Ubuntu virtual machine
- Checks out your code
- Sets up Node.js version 18 with npm caching
- Installs dependencies
- Runs tests (allows continuing even if tests fail, since they might not be implemented yet)
build-and-push:
needs: test
runs-on: ubuntu-latest
steps:
# ... setup steps ...
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/auth-proxy:latest
ghcr.io/${{ github.repository_owner }}/auth-proxy:${{ github.sha }}
This job:
- Only runs after the test job completes
- Builds the Docker image using your Dockerfile
- Pushes the image to GitHub Container Registry with two tags:
- 'latest': Always points to the most recent successful build
- A tag using the commit SHA for versioning and rollback capability
The deployment section is more complex:
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
# ... setup steps ...
- name: Create deployment directory and files on droplet
uses: appleboy/ssh-action@master
with:
host: authproxy.nss.team
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
mkdir -p /opt/authproxy
This creates a directory on the server if it doesn't already exist.
- name: Deploy to Digital Ocean droplet
uses: appleboy/ssh-action@master
with:
host: authproxy.nss.team
username: root
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /opt/authproxy
# Create or update .env file
cat > .env << EOF
PORT=3000
OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
ALLOWED_ORIGINS=https://nashville-software-school.github.io
NODE_ENV=production
EOF
This:
- Connects to your DigitalOcean server using SSH
- Changes to the application directory
- Creates an environment file (.env) with sensitive configuration values pulled from GitHub repository secrets
# Create or update docker-compose.yml
cat > docker-compose.yml << EOF
services:
authproxy:
image: ghcr.io/${{ github.repository_owner }}/auth-proxy:${{ github.sha }}
ports:
- "80:80"
- "443:443"
env_file:
- .env
volumes:
- /etc/letsencrypt/live/authproxy.nss.team/fullchain.pem:/etc/ssl/certs/authproxy.fullchain.pem:ro
- /etc/letsencrypt/live/authproxy.nss.team/privkey.pem:/etc/ssl/private/authproxy.privkey.pem:ro
restart: unless-stopped
EOF
This creates a Docker Compose file that:
- Uses the image built in the previous step
- Maps ports 80 and 443 from the host to the container
- Uses the environment variables from the .env file
- Mounts SSL certificates from Let's Encrypt into the container
- Configures the container to restart automatically unless explicitly stopped
# Log into GitHub Container Registry
echo ${{ secrets.GH_TOKEN }} | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
# Deploy
docker compose pull
docker compose up -d
# Clean up old images
docker image prune -f
These final commands:
- Log into GitHub Container Registry to pull the private image
- Pull the latest image
- Start or update the container in detached mode (-d)
- Clean up old, unused Docker images to free up disk space
If you want to run your own instance of this proxy:
-
Create a GitHub OAuth App:
- Go to GitHub > Settings > Developer Settings > OAuth Apps > New OAuth App
- Set the Authorization callback URL to your client application's URL
- Note your Client ID and Client Secret
-
Clone this repository:
git clone <repository-url> cd <repository-directory>
-
Create a .env file:
PORT=3000 OAUTH_CLIENT_ID=your_github_client_id OAUTH_CLIENT_SECRET=your_github_client_secret ALLOWED_ORIGINS=https://your-frontend-app.com,http://localhost:3000
-
Install dependencies:
npm install
-
Run the application:
For development with auto-restart on file changes:
npm run dev
-
Verify the server is running:
- The server will output:
OAuth proxy server running on port 3000
(or your configured port) - You can test the health endpoint by visiting:
http://localhost:3000/health
- The server will output:
Contributions are welcome! Here's how you can contribute:
- Fork the repository
- Create a feature branch:
git checkout -b my-new-feature
- Make your changes
- Run tests:
npm test
- Commit your changes:
git commit -am 'Add some feature'
- Push to the branch:
git push origin my-new-feature
- Submit a pull request
If you encounter issues with Nginx configuration, check for syntax errors with:
docker exec <container-id> nginx -t
To view logs:
docker logs <container-id>
This project is licensed under the MIT License - see the LICENSE file for details.