439 lines
8.7 KiB
Markdown
439 lines
8.7 KiB
Markdown
# 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.
|