SurillyaID OpenID Connect (OIDC)
Documentation on how you can easily integrate SurillyaID sign-in into your applications!
Use OpenID Connect
OpenID Connect is the recommended protocol for SurillyaID integration. It provides:
- 12 scopes for user data (see below)
- ID Tokens (JWT) with built-in signature verification
- Standard claims following OIDC specifications
- Better library support across all major languages and frameworks
OAuth2 is also supported but has only basic authentication without the rich scope ecosystem.
Quick Start
Prerequisites
- Create a developer account at developer.surillya.com
- Create an application and get your Client ID and Secret
- Configure your redirect URI
- Always include
openidscope to enable OIDC features
OpenID Connect Discovery
SurillyaID supports easy OIDC Discovery:
Most OIDC libraries can auto-configure using this endpoint. No manual endpoint configuration needed!
API Endpoints
Authorization Endpoint
Initiates the OAuth flow and prompts users to authorize your application
Token Endpoint
Exchanges authorization codes for access tokens
UserInfo Endpoint
Returns user information based on granted scopes
JWKS Endpoint
Public keys for verifying JWT signatures
Token Revocation
Revoke access tokens (RFC 7009)
List of all available claims
Here's a complete list of all 30 claims available through SurillyaID OIDC. Claims are returned based on the scopes you request:
User Identity & Profile Claims
| Claim | Type | Description | Required Scope |
|---|---|---|---|
sub |
string | Subject identifier (user ID) | openid |
id |
string | User ID (alias for sub) | openid |
name |
string | Full display name | profile |
username |
string | Unique username | profile |
preferred_username |
string | Preferred username (same as username) | profile |
email |
string | Email address | |
email_verified |
boolean | Whether email is verified | |
picture |
URL | Profile picture URL | profile |
banner |
URL | Profile banner image URL | profile |
bio |
string | User biography/about text | profile |
verified |
boolean | Whether user has verified badge | profile |
Extended Claims
| Claim | Type | Description | Required Scope |
|---|---|---|---|
location |
string | Geographic location | location |
age |
integer | User's age | demographics |
gender |
string | User's gender | demographics |
profession |
string | User's profession/occupation | professional |
education |
string | Education level/institution | professional |
minecraft_username |
string | Minecraft username | minecraft |
minecraft_uuid |
string | Minecraft player UUID | minecraft |
spotify_track |
string | Currently playing Spotify track | spotify |
social_links |
JSON array | Array of social media profile links | social |
material_colors |
JSON object | Material You dynamic color palette | theme |
theme_color1 |
string | Primary theme color (hex) | theme |
theme_color2 |
string | Secondary theme color (hex) | theme |
citizenship |
string | MDU citizenship status | citizenship |
is_supporter |
boolean | Whether user is a supporter | supporter |
supporter_since |
datetime | When user became a supporter | supporter |
JWT Standard Claims (ID Token only)
| Claim | Type | Description |
|---|---|---|
iss |
string | Issuer (https://surillya.com/id) |
aud |
string | Audience (your client_id) |
exp |
timestamp | Expiration time |
iat |
timestamp | Issued at time |
OIDC Authorization Flow (with PKCE)
This is the recommended flow for all applications. PKCE provides additional security for both web and mobile apps.
Step 1: Generate PKCE Challenge
// Generate code verifier (random string)
code_verifier = base64url(random(32 bytes))
// Generate code challenge (SHA-256 hash)
code_challenge = base64url(sha256(code_verifier))
Step 2: Redirect to Authorization Endpoint
Include the openid scope plus any additional scopes you need:
https://surillya.com/id/authorize.php?
client_id=YOUR_CLIENT_ID&
redirect_uri=YOUR_REDIRECT_URI&
response_type=code&
scope=openid profile email&
state=RANDOM_STATE&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256&
nonce=RANDOM_NONCE
Step 3: Exchange Code for Tokens
Response includes both Access Token and ID Token (JWT):
POST https://surillya.com/id/token.php
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=YOUR_REDIRECT_URI&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
code_verifier=CODE_VERIFIER
// Response:
{
"access_token": "...",
"id_token": "...", // JWT with user claims
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile email"
}
Step 4: Get User Information
Two options - decode ID Token (faster) or call UserInfo endpoint:
// Option 1: Decode and verify ID Token JWT (recommended)
// Contains claims based on requested scopes
// Verify signature using JWKS endpoint
// Option 2: Call UserInfo endpoint
GET https://surillya.com/id/userinfo.php
Authorization: Bearer ACCESS_TOKEN
OIDC Scopes (12 Available)
SurillyaID provides 12 comprehensive scopes for accessing different types of user data. Always include openid to enable OIDC.
Core OIDC Scopes (Standard)
| Scope | Description | Claims Returned |
|---|---|---|
openid |
Required for OIDC - User identifier | sub, id |
profile |
Basic profile information | name, username, picture, banner, bio, verified |
email |
Email address and verification status | email, email_verified |
Extended Scopes (SurillyaID-Specific)
| Scope | Description | Claims Returned |
|---|---|---|
location |
User's location | location |
demographics |
Age and gender information | age, gender |
professional |
Career and education data | profession, education |
minecraft |
Minecraft profile (username & UUID) | minecraft_username, minecraft_uuid |
spotify |
Currently playing Spotify track | spotify_track |
social |
Social media profile links | social_links (JSON array) |
theme |
Material You theme colors & legacy colors | material_colors (object), theme_color1, theme_color2 |
citizenship |
Surillya citizenship status | citizenship |
supporter |
Supporter/donor status | is_supporter, supporter_since |
Scope Best Practices
- Always request
openidas the first scope to enable OIDC - Request only what you need - Users see every scope you request
- Combine scopes:
openid profile email demographics - Common combinations:
- Basic login:
openid profile email - Gaming platform:
openid profile email minecraft - Social app:
openid profile email social demographics - Music app:
openid profile spotify
- Basic login:
Using OIDC Libraries (Recommended)
For production applications, we strongly recommend using established OIDC libraries. These handle critical security features (PKCE, State validation, JWT signature verification) automatically.
Node.js (Express + Passport)
This example uses passport-openidconnect for authentication and connect-redis for secure session storage.
1. Installation:
npm install express express-session passport openid-client dotenv connect-redis redis
Requirement: You must have a Redis server running locally or accessible via network.
2. Setup Environment Variables (.env):
Create a .env file in your project root. Do not commit this file to version control.
# Get these from the Surillya Developer Dashboard
SURILLYA_CLIENT_ID=your_client_id_here
SURILLYA_CLIENT_SECRET=your_client_secret_here
# Generate a strong random string (e.g. `openssl rand -hex 32`)
SESSION_SECRET=long_random_string_here
# Production Settings
NODE_ENV=production
3. Application Code (server.js):
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const { Issuer, Strategy } = require('openid-client');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const app = express();
// --- 1. Database & Session Setup ---
// Initialize Redis client for persistent session storage
const redisClient = createClient();
redisClient.connect().catch(console.error);
// Initialize session store
const sessionStore = new RedisStore({
client: redisClient,
prefix: "surillya_sess:",
});
// Configure session middleware
app.use(session({
store: sessionStore,
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // true in production (requires HTTPS)
httpOnly: true, // Prevents client-side JS from reading the cookie
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// --- 2. Authentication Setup ---
app.use(passport.initialize());
app.use(passport.session());
// Serialization: How to store user in session
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
async function main() {
// Discover OIDC endpoints automatically
const issuer = await Issuer.discover('https://surillya.com/id/.well-known/openid-configuration');
const client = new issuer.Client({
client_id: process.env.SURILLYA_CLIENT_ID,
client_secret: process.env.SURILLYA_CLIENT_SECRET,
redirect_uris: ['http://localhost:3000/callback'],
response_types: ['code']
});
passport.use('oidc', new Strategy({
client,
params: {
scope: 'openid profile email',
code_challenge_method: 'S256'
},
usePKCE: true
}, (tokenSet, userinfo, done) => {
return done(null, userinfo);
}));
// --- 3. Routes ---
// Login initiates the OIDC flow
app.get('/login', passport.authenticate('oidc'));
// Callback handles the response from SurillyaID
app.get('/callback', passport.authenticate('oidc', {
successRedirect: '/dashboard',
failureRedirect: '/login?error=failed'
}));
// Protected route
app.get('/dashboard', (req, res) => {
if (!req.isAuthenticated()) return res.redirect('/login');
res.send(`Hello ${req.user.name}! <a href="/logout">Logout</a>`);
});
// Logout clears session
app.get('/logout', (req, res, next) => {
req.logout(err => {
if (err) return next(err);
res.redirect('/');
});
});
app.listen(3000, () => console.log('Server running on http://localhost:3000'));
}
main().catch(console.error);
Python (Flask + Authlib)
This example uses Authlib to handle the OIDC flow in a Flask application.
1. Installation:
pip install Authlib Flask python-dotenv requests
2. Configuration (.env):
Create a .env file in your project root. Do not commit this file to version control.
# Get these from the Surillya Developer Dashboard
SURILLYA_CLIENT_ID=your_client_id_here
SURILLYA_CLIENT_SECRET=your_client_secret_here
# Generate a strong secret key (e.g. `python -c 'import secrets; print(secrets.token_hex())'`)
FLASK_SECRET_KEY=your_generated_secret_key
3. Application Code (app.py):
from flask import Flask, redirect, url_for, session
from authlib.integrations.flask_client import OAuth
import os
from dotenv import load_dotenv
# Load environment variables from .env
load_dotenv()
app = Flask(__name__)
# --- 1. Security Configuration ---
app.secret_key = os.getenv('FLASK_SECRET_KEY')
# Production security headers for cookies
app.config.update(
SESSION_COOKIE_SECURE=True, # Requires HTTPS
SESSION_COOKIE_HTTPONLY=True, # Prevent XSS access to cookies
SESSION_COOKIE_SAMESITE='Lax' # CSRF protection
)
oauth = OAuth(app)
# --- 2. Provider Configuration ---
surillya = oauth.register(
name='surillya',
server_metadata_url='https://surillya.com/id/.well-known/openid-configuration',
client_id=os.getenv('SURILLYA_CLIENT_ID'),
client_secret=os.getenv('SURILLYA_CLIENT_SECRET'),
client_kwargs={
'scope': 'openid profile email',
'code_challenge_method': 'S256' # Enforce PKCE
}
)
# --- 3. Routes ---
@app.route('/')
def homepage():
user = session.get('user')
if user:
return f"Hello, {user['name']}! <a href='/logout'>Logout</a>"
return '<a href="/login">Login with SurillyaID</a>'
@app.route('/login')
def login():
# Redirects user to SurillyaID authorization page
redirect_uri = url_for('auth_callback', _external=True)
return surillya.authorize_redirect(redirect_uri)
@app.route('/callback')
def auth_callback():
try:
# Exchange code for token and get user info
token = surillya.authorize_access_token()
user = token['userinfo']
session['user'] = user
return redirect('/')
except Exception as e:
return f"Authentication failed: {str(e)}"
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect('/')
if __name__ == '__main__':
# WARNING: Do not use `app.run` in production.
# Use a WSGI server like Gunicorn: `gunicorn -w 4 app:app`
app.run(port=5000)
PHP (jumbojett/openid-connect-php)
This example shows a secure structure preventing public access to credentials.
1. Directory Structure:
project/
├── .env # Credentials (NOT PUBLIC)
├── vendor/ # Dependencies
├── surillya-init.php # Configuration
├── surillya-protect.php # Include to force login
├── public/ # Web root
│ ├── index.php
│ ├── surillya-login.php
│ └── surillya-callback.php
└── composer.json
2. Installation:
composer require jumbojett/openid-connect-php vlucas/phpdotenv
3. surillya-init.php (Configuration):
<?php
require __DIR__ . '/vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
// Secure session params
session_set_cookie_params([
'lifetime' => 86400,
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]);
session_start();
$oidc = new OpenIDConnectClient(
'https://surillya.com/id',
$_ENV['SURILLYA_CLIENT_ID'],
$_ENV['SURILLYA_CLIENT_SECRET']
);
$oidc->setRedirectURL('http://localhost:8000/surillya-callback.php');
$oidc->addScope(['openid', 'profile', 'email']);
$oidc->setCodeChallengeMethod('S256');
?>
4. surillya-login.php (Login Endpoint):
<?php
require dirname(__DIR__) . '/surillya-init.php';
$oidc->authenticate(); // Redirects to Surillya Login
?>
5. surillya-callback.php (Callback Handler):
<?php
require dirname(__DIR__) . '/surillya-init.php';
// Process the callback
$oidc->authenticate();
// Save user info
$_SESSION['user'] = $oidc->requestUserInfo();
header('Location: /index.php');
exit;
?>
6. surillya-protect.php (Protection Prefix):
<?php
require_once __DIR__ . '/surillya-init.php';
if (!isset($_SESSION['user'])) {
header('Location: /surillya-login.php');
exit;
}
?>
7. index.php (Public Page):
<?php
require dirname(__DIR__) . '/surillya-init.php';
?>
<?php if (isset($_SESSION['user'])): ?>
<h1>Hello, <?= htmlspecialchars($_SESSION['user']->name) ?></h1>
<p>Email: <?= htmlspecialchars($_SESSION['user']->email) ?></p>
<?php else: ?>
<h1>Welcome</h1>
<a href="/surillya-login.php">Login with SurillyaID</a>
<?php endif; ?>
Need Help?
If you have questions or need assistance:
- Message on the MPL at (638) 125-7874
- Email contact@surillya.com
- Check your application analytics for debugging
- Review error messages in the OAuth flow