first commit
This commit is contained in:
438
docs/QUICKSTART.md
Normal file
438
docs/QUICKSTART.md
Normal file
@ -0,0 +1,438 @@
|
||||
# Quick Start Guide
|
||||
|
||||
Get your application integrated with this OIDC provider in 10 minutes.
|
||||
|
||||
---
|
||||
|
||||
## For the Impatient
|
||||
|
||||
```bash
|
||||
# 1. Get credentials from admin panel
|
||||
https://your-idp.com/admin/login
|
||||
|
||||
# 2. Add to your app (Python example)
|
||||
pip install authlib flask
|
||||
|
||||
# 3. Copy this code
|
||||
# (see Python example below)
|
||||
|
||||
# 4. Done! Users can now log in via OIDC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- ✅ OIDC Provider deployed and accessible
|
||||
- ✅ Admin access to register your client
|
||||
- ✅ A web application with a backend (Node.js, Python, PHP, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Register Your Application (2 minutes)
|
||||
|
||||
### Via Admin Panel
|
||||
|
||||
1. **Navigate** to: `https://your-idp.com/admin/login`
|
||||
2. **Login** with admin credentials
|
||||
3. **Go to** "Clients" → "Create New Client"
|
||||
4. **Fill in**:
|
||||
- Client Name: `My App`
|
||||
- Redirect URIs: `http://localhost:3000/callback` (one per line)
|
||||
- Allowed Scopes: `openid, profile, email`
|
||||
5. **Click** "Create"
|
||||
6. **Copy** your credentials:
|
||||
```
|
||||
Client ID: abc123def456
|
||||
Client Secret: xyz789... (⚠️ save this - shown only once!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Choose Your Integration Method (1 minute)
|
||||
|
||||
Pick the method that matches your tech stack:
|
||||
|
||||
| If you use... | Go to |
|
||||
|---------------|-------|
|
||||
| Python + Flask | [Python Example](#python-flask) |
|
||||
| Node.js + Express | [Node.js Example](#nodejs-express) |
|
||||
| PHP + Laravel | [PHP Example](#php-laravel) |
|
||||
| Any other | [Generic HTTP Flow](#generic-http-flow) |
|
||||
|
||||
---
|
||||
|
||||
## Python (Flask)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install flask authlib requests
|
||||
```
|
||||
|
||||
### Code (`app.py`)
|
||||
|
||||
```python
|
||||
from flask import Flask, redirect, url_for, session, jsonify
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.urandom(24)
|
||||
|
||||
# Configure OIDC
|
||||
oauth = OAuth(app)
|
||||
oauth.register(
|
||||
name='myidp',
|
||||
client_id='YOUR_CLIENT_ID',
|
||||
client_secret='YOUR_CLIENT_SECRET',
|
||||
server_metadata_url='https://your-idp.com/.well-known/openid-configuration',
|
||||
client_kwargs={'scope': 'openid profile email'}
|
||||
)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
user = session.get('user')
|
||||
if user:
|
||||
return jsonify(user)
|
||||
return '<a href="/login">Login with OIDC</a>'
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
redirect_uri = url_for('callback', _external=True)
|
||||
return oauth.myidp.authorize_redirect(redirect_uri)
|
||||
|
||||
@app.route('/callback')
|
||||
def callback():
|
||||
token = oauth.myidp.authorize_access_token()
|
||||
session['user'] = token['userinfo']
|
||||
return redirect('/')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('user', None)
|
||||
return redirect('/')
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=3000, debug=True)
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
python app.py
|
||||
# Open http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js (Express)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
npm install express express-session passport openid-client
|
||||
```
|
||||
|
||||
### Code (`server.js`)
|
||||
|
||||
```javascript
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const passport = require('passport');
|
||||
const { Issuer, Strategy } = require('openid-client');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(session({
|
||||
secret: 'keyboard cat',
|
||||
resave: false,
|
||||
saveUninitialized: true
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.serializeUser((user, done) => done(null, user));
|
||||
passport.deserializeUser((user, done) => done(null, user));
|
||||
|
||||
// Discover and configure OIDC
|
||||
Issuer.discover('https://your-idp.com/.well-known/openid-configuration')
|
||||
.then(issuer => {
|
||||
const client = new issuer.Client({
|
||||
client_id: 'YOUR_CLIENT_ID',
|
||||
client_secret: 'YOUR_CLIENT_SECRET',
|
||||
redirect_uris: ['http://localhost:3000/callback'],
|
||||
response_types: ['code'],
|
||||
});
|
||||
|
||||
passport.use('oidc', new Strategy({ client }, (tokenSet, userinfo, done) => {
|
||||
return done(null, userinfo);
|
||||
}));
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
if (req.isAuthenticated()) {
|
||||
res.send(`<h1>Hello, ${req.user.name}!</h1><a href="/logout">Logout</a>`);
|
||||
} else {
|
||||
res.send('<a href="/login">Login with OIDC</a>');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/login', passport.authenticate('oidc'));
|
||||
|
||||
app.get('/callback',
|
||||
passport.authenticate('oidc', { failureRedirect: '/' }),
|
||||
(req, res) => res.redirect('/')
|
||||
);
|
||||
|
||||
app.get('/logout', (req, res) => {
|
||||
req.logout(() => res.redirect('/'));
|
||||
});
|
||||
|
||||
app.listen(3000, () => console.log('App running on http://localhost:3000'));
|
||||
});
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
node server.js
|
||||
# Open http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PHP (Laravel)
|
||||
|
||||
### Install Socialite
|
||||
|
||||
```bash
|
||||
composer require laravel/socialite
|
||||
composer require socialiteproviders/oidc
|
||||
```
|
||||
|
||||
### Configure (`config/services.php`)
|
||||
|
||||
```php
|
||||
'oidc' => [
|
||||
'client_id' => env('OIDC_CLIENT_ID'),
|
||||
'client_secret' => env('OIDC_CLIENT_SECRET'),
|
||||
'redirect' => env('OIDC_REDIRECT_URI'),
|
||||
'base_url' => env('OIDC_ISSUER'),
|
||||
],
|
||||
```
|
||||
|
||||
### Environment (`.env`)
|
||||
|
||||
```bash
|
||||
OIDC_CLIENT_ID=YOUR_CLIENT_ID
|
||||
OIDC_CLIENT_SECRET=YOUR_CLIENT_SECRET
|
||||
OIDC_REDIRECT_URI=http://localhost:8000/callback
|
||||
OIDC_ISSUER=https://your-idp.com
|
||||
```
|
||||
|
||||
### Routes (`routes/web.php`)
|
||||
|
||||
```php
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
Route::get('/login', function () {
|
||||
return Socialite::driver('oidc')->redirect();
|
||||
});
|
||||
|
||||
Route::get('/callback', function () {
|
||||
$user = Socialite::driver('oidc')->user();
|
||||
|
||||
// Find or create user in database
|
||||
$localUser = User::updateOrCreate(
|
||||
['email' => $user->email],
|
||||
['name' => $user->name]
|
||||
);
|
||||
|
||||
Auth::login($localUser);
|
||||
|
||||
return redirect('/dashboard');
|
||||
});
|
||||
|
||||
Route::get('/logout', function () {
|
||||
Auth::logout();
|
||||
return redirect('/');
|
||||
});
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
php artisan serve
|
||||
# Open http://localhost:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Generic HTTP Flow
|
||||
|
||||
If you can't use a library, here's the manual flow:
|
||||
|
||||
### Step 1: Redirect to Authorization Endpoint
|
||||
|
||||
```http
|
||||
GET https://your-idp.com/authorize?
|
||||
client_id=YOUR_CLIENT_ID&
|
||||
redirect_uri=http://localhost:3000/callback&
|
||||
response_type=code&
|
||||
scope=openid%20profile%20email&
|
||||
state=RANDOM_STATE_TOKEN
|
||||
```
|
||||
|
||||
### Step 2: Handle Callback
|
||||
|
||||
User is redirected back with a code:
|
||||
```
|
||||
http://localhost:3000/callback?code=ABC123&state=RANDOM_STATE_TOKEN
|
||||
```
|
||||
|
||||
**Verify state token** to prevent CSRF!
|
||||
|
||||
### Step 3: Exchange Code for Token
|
||||
|
||||
```bash
|
||||
curl -X POST https://your-idp.com/token \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "grant_type=authorization_code" \
|
||||
-d "code=ABC123" \
|
||||
-d "redirect_uri=http://localhost:3000/callback" \
|
||||
-d "client_id=YOUR_CLIENT_ID" \
|
||||
-d "client_secret=YOUR_CLIENT_SECRET"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGci...",
|
||||
"id_token": "eyJhbGci...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Get User Info
|
||||
|
||||
```bash
|
||||
curl https://your-idp.com/userinfo \
|
||||
-H "Authorization: Bearer ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"sub": "user-123",
|
||||
"email": "john@example.com",
|
||||
"name": "John Doe",
|
||||
"preferred_username": "john"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Integration
|
||||
|
||||
### 1. Start Your App
|
||||
|
||||
```bash
|
||||
# Your app should now be running on localhost
|
||||
```
|
||||
|
||||
### 2. Click "Login"
|
||||
|
||||
Navigate to your app's login link. You should be redirected to:
|
||||
```
|
||||
https://your-idp.com/authorize?client_id=...
|
||||
```
|
||||
|
||||
### 3. Login
|
||||
|
||||
Use test credentials:
|
||||
```
|
||||
Username: test
|
||||
Password: test123
|
||||
```
|
||||
|
||||
### 4. Verify
|
||||
|
||||
After login, you should:
|
||||
- ✅ Be redirected back to your app
|
||||
- ✅ See user information
|
||||
- ✅ Have an active session
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "Invalid Redirect URI"
|
||||
|
||||
**Problem**: Redirect URI doesn't match.
|
||||
|
||||
**Fix**:
|
||||
1. Check exact match (including trailing slash)
|
||||
2. Update in admin panel if needed
|
||||
|
||||
### "Invalid Client"
|
||||
|
||||
**Problem**: Wrong client ID or secret.
|
||||
|
||||
**Fix**:
|
||||
1. Double-check credentials
|
||||
2. No extra spaces or line breaks
|
||||
|
||||
### "Connection Refused"
|
||||
|
||||
**Problem**: OIDC provider not accessible.
|
||||
|
||||
**Fix**:
|
||||
1. Verify provider is running: `curl https://your-idp.com/health`
|
||||
2. Check network/firewall
|
||||
|
||||
### CORS Errors (for SPAs)
|
||||
|
||||
**Problem**: Browser blocks cross-origin requests.
|
||||
|
||||
**Solution**: Don't call OIDC endpoints from frontend. Use a backend proxy.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once basic login works:
|
||||
|
||||
1. **Add User Persistence**: Store user in your database
|
||||
2. **Handle Logout**: Clear session and optionally redirect to IdP logout
|
||||
3. **Refresh Tokens**: Implement token refresh (when available)
|
||||
4. **Error Handling**: Add proper error pages
|
||||
5. **Production Setup**: Use HTTPS, secure cookies
|
||||
|
||||
---
|
||||
|
||||
## Complete Examples
|
||||
|
||||
Check out complete example applications:
|
||||
|
||||
- **Python Flask**: `examples/python-flask/` (coming soon)
|
||||
- **Node.js Express**: `examples/nodejs-express/` (coming soon)
|
||||
- **PHP Laravel**: `examples/php-laravel/` (coming soon)
|
||||
|
||||
---
|
||||
|
||||
## Need Help?
|
||||
|
||||
- 📖 [Full API Guide](API_GUIDE.md)
|
||||
- 🏗️ [Architecture](ARCHITECTURE.md)
|
||||
- 🚀 [Deployment](deployment.md)
|
||||
- 🐛 Report issues on GitHub
|
||||
|
||||
---
|
||||
|
||||
**You're all set!** 🎉
|
||||
|
||||
Your users can now log in via OIDC in just a few clicks.
|
||||
Reference in New Issue
Block a user