Разработка OIDC приложений

ℹ️ О документе

В этом разделе представлено практическое руководство по разработке приложений, интегрированных с IDENTYX через протокол OpenID Connect. Вы найдете примеры кода на различных языках программирования, рекомендации по выбору библиотек и лучшие практики реализации.

Подготовка к разработке

Перед началом разработки убедитесь, что у вас есть:

  • Зарегистрированное приложение в IDENTYX — перейдите в раздел Создание OIDC приложения для регистрации
  • Client ID и Client Secret — полученные при регистрации приложения
  • Redirect URI — URL вашего приложения для обработки обратного вызова после аутентификации
  • Базовый URL IDENTYX сервера — адрес вашего развернутого экземпляра IDENTYX
⚠️ Безопасность

Никогда не публикуйте client_secret в открытых репозиториях, клиентском коде или конфигурационных файлах, доступных публично. Храните секреты в переменных окружения или защищенных хранилищах.

Выбор библиотеки OIDC

Для упрощения интеграции рекомендуется использовать готовые библиотеки OpenID Connect для вашего языка программирования. Эти библиотеки берут на себя сложности протокола, обработку токенов и валидацию.

Язык/Фреймворк Библиотека Ссылка
PHP jumbojett/openid-connect-php GitHub
Python authlib GitHub
Node.js openid-client GitHub
Java/Spring Spring Security OAuth2 Официальный сайт
C# / .NET IdentityModel.OidcClient GitHub
Go coreos/go-oidc GitHub
Ruby omniauth-openid-connect GitHub
ℹ️ Совет

Большинство современных библиотек поддерживают автоматическое обнаружение конфигурации через эндпоинт /.well-known/openid-configuration, что значительно упрощает настройку.

Пример на PHP

Рассмотрим пример интеграции с использованием популярной библиотеки jumbojett/openid-connect-php.

Установка

Установите библиотеку через Composer:

composer require jumbojett/openid-connect-php

Базовая конфигурация

<?php

require 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

// Создаем клиент OIDC
$oidc = new OpenIDConnectClient(
    'https://ваш-identyx-сервер', // URL провайдера
    'ваш_client_id',               // Client ID
    'ваш_client_secret'            // Client Secret
);

// Настраиваем redirect URI
$oidc->setRedirectURL('https://ваше-приложение/callback');

// Запрашиваем необходимые scopes
$oidc->addScope(['openid', 'profile', 'email', 'permissions']);

// Выполняем аутентификацию
$oidc->authenticate();

// Получаем информацию о пользователе
$user_info = $oidc->requestUserInfo();

// Выводим данные пользователя
echo "Пользователь: " . $user_info->name . "<br>";
echo "Email: " . $user_info->email . "<br>";
echo "ID: " . $user_info->sub . "<br>";

// Получаем права доступа
$permissions = $user_info->permissions ?? [];
echo "Права: " . implode(', ', $permissions);

Расширенный пример с обработкой сессии

<?php

session_start();

require 'vendor/autoload.php';

use Jumbojett\OpenIDConnectClient;

// Проверяем, авторизован ли пользователь
if (isset($_SESSION['user_info'])) {
    // Пользователь уже авторизован
    $user_info = $_SESSION['user_info'];
    echo "Добро пожаловать, " . htmlspecialchars($user_info['name']);
    exit;
}

try {
    $oidc = new OpenIDConnectClient(
        getenv('IDENTYX_URL'),
        getenv('CLIENT_ID'),
        getenv('CLIENT_SECRET')
    );
    
    $oidc->setRedirectURL('https://ваше-приложение/callback');
    $oidc->addScope(['openid', 'profile', 'email', 'permissions']);
    
    // Аутентификация
    $oidc->authenticate();
    
    // Получаем информацию о пользователе
    $user_info = $oidc->requestUserInfo();
    
    // Сохраняем в сессию
    $_SESSION['user_info'] = [
        'sub' => $user_info->sub,
        'name' => $user_info->name,
        'email' => $user_info->email,
        'permissions' => $user_info->permissions ?? []
    ];
    
    // Сохраняем токены для последующего обновления
    $_SESSION['access_token'] = $oidc->getAccessToken();
    $_SESSION['refresh_token'] = $oidc->getRefreshToken();
    
    // Перенаправляем на главную страницу
    header('Location: /');
    exit;
    
} catch (Exception $e) {
    echo "Ошибка аутентификации: " . htmlspecialchars($e->getMessage());
    exit;
}

Пример на Python

Пример интеграции с использованием библиотеки authlib и Flask фреймворка.

Установка

pip install authlib flask requests

Код приложения

from flask import Flask, redirect, url_for, session
from authlib.integrations.flask_client import OAuth
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)

# Настройка OAuth
oauth = OAuth(app)

identyx = oauth.register(
    name='identyx',
    client_id=os.getenv('CLIENT_ID'),
    client_secret=os.getenv('CLIENT_SECRET'),
    server_metadata_url='https://ваш-identyx-сервер/.well-known/openid-configuration',
    client_kwargs={
        'scope': 'openid profile email permissions'
    }
)

@app.route('/')
def home():
    user = session.get('user')
    if user:
        return f'Привет, {user["name"]}! <a href="/logout">Выход</a>'
    return '<a href="/login">Войти через IDENTYX</a>'

@app.route('/login')
def login():
    redirect_uri = url_for('callback', _external=True)
    return identyx.authorize_redirect(redirect_uri)

@app.route('/callback')
def callback():
    token = identyx.authorize_access_token()
    user_info = token.get('userinfo')
    
    # Сохраняем информацию о пользователе в сессию
    session['user'] = {
        'sub': user_info['sub'],
        'name': user_info['name'],
        'email': user_info['email'],
        'permissions': user_info.get('permissions', [])
    }
    session['token'] = token
    
    return redirect('/')

@app.route('/logout')
def logout():
    session.clear()
    return redirect('/')

@app.route('/api/protected')
def protected():
    # Пример защищенного эндпоинта
    user = session.get('user')
    if not user:
        return {'error': 'Unauthorized'}, 401
    
    # Проверяем права доступа
    required_permission = '/api:/read'
    if required_permission not in user.get('permissions', []):
        return {'error': 'Forbidden'}, 403
    
    return {'message': 'Доступ разрешен', 'user': user['name']}

if __name__ == '__main__':
    app.run(debug=True, port=5000)

Пример на Node.js

Пример интеграции с использованием библиотеки openid-client и Express фреймворка.

Установка

npm install express express-session openid-client

Код приложения

const express = require('express');
const session = require('express-session');
const { Issuer } = require('openid-client');

const app = express();

// Настройка сессий
app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // Установите true при использовании HTTPS
}));

// Инициализация OIDC клиента
let client;

(async () => {
  const identyxIssuer = await Issuer.discover('https://ваш-identyx-сервер/.well-known/openid-configuration');
  
  client = new identyxIssuer.Client({
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    redirect_uris: ['http://localhost:3000/callback'],
    response_types: ['code'],
  });
  
  console.log('OIDC клиент инициализирован');
})();

// Главная страница
app.get('/', (req, res) => {
  if (req.session.user) {
    res.send(`
      <h1>Привет, ${req.session.user.name}!</h1>
      <p>Email: ${req.session.user.email}</p>
      <p>Права: ${req.session.user.permissions.join(', ')}</p>
      <a href="/logout">Выход</a>
    `);
  } else {
    res.send('<a href="/login">Войти через IDENTYX</a>');
  }
});

// Начало аутентификации
app.get('/login', (req, res) => {
  const authorizationUrl = client.authorizationUrl({
    scope: 'openid profile email permissions',
    state: 'random_state_value',
  });
  res.redirect(authorizationUrl);
});

// Обработка callback
app.get('/callback', async (req, res) => {
  try {
    const params = client.callbackParams(req);
    const tokenSet = await client.callback('http://localhost:3000/callback', params, {
      state: 'random_state_value',
    });
    
    // Получаем информацию о пользователе
    const userinfo = await client.userinfo(tokenSet.access_token);
    
    // Сохраняем в сессию
    req.session.user = {
      sub: userinfo.sub,
      name: userinfo.name,
      email: userinfo.email,
      permissions: userinfo.permissions || []
    };
    req.session.tokens = {
      access_token: tokenSet.access_token,
      refresh_token: tokenSet.refresh_token
    };
    
    res.redirect('/');
  } catch (err) {
    console.error('Ошибка аутентификации:', err);
    res.status(500).send('Ошибка аутентификации');
  }
});

// Выход
app.get('/logout', (req, res) => {
  req.session.destroy();
  const logoutUrl = `https://ваш-identyx-сервер/api/service/oidc/end-session?client_id=${process.env.CLIENT_ID}&post_logout_redirect_uri=http://localhost:3000/`;
  res.redirect(logoutUrl);
});

// Защищенный эндпоинт
app.get('/api/data', (req, res) => {
  if (!req.session.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  // Проверка прав доступа
  const requiredPermission = '/data:/read';
  if (!req.session.user.permissions.includes(requiredPermission)) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  
  res.json({ message: 'Данные доступны', user: req.session.user.name });
});

app.listen(3000, () => {
  console.log('Сервер запущен на http://localhost:3000');
});

Работа с токенами

Access Token

Access Token используется для доступа к защищенным ресурсам. Срок действия токена ограничен (обычно 1 час).

Используйте Access Token в заголовке запросов к защищенным эндпоинтам:

Authorization: Bearer ваш_access_token

Обновление токенов с Refresh Token

Когда Access Token истекает, используйте Refresh Token для получения нового набора токенов без повторной аутентификации пользователя.

Пример на PHP:

<?php

function refreshAccessToken($refresh_token) {
    $data = [
        'grant_type' => 'refresh_token',
        'refresh_token' => $refresh_token,
        'client_id' => getenv('CLIENT_ID'),
        'client_secret' => getenv('CLIENT_SECRET')
    ];
    
    $ch = curl_init('https://ваш-identyx-сервер/api/service/oidc/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $result = json_decode($response, true);
    
    return [
        'access_token' => $result['access_token'],
        'refresh_token' => $result['refresh_token'],
        'expires_in' => $result['expires_in']
    ];
}

// Использование
$new_tokens = refreshAccessToken($_SESSION['refresh_token']);
$_SESSION['access_token'] = $new_tokens['access_token'];
$_SESSION['refresh_token'] = $new_tokens['refresh_token'];

ID Token и JWT

ID Token — это JWT (JSON Web Token), содержащий информацию о пользователе. Вы можете декодировать его для получения базовых данных без дополнительных запросов.

Пример декодирования JWT на JavaScript:

function parseJwt(token) {
    const base64Url = token.split('.')[1];
    const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
    const jsonPayload = decodeURIComponent(
        atob(base64).split('').map(c => 
            '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
        ).join('')
    );
    return JSON.parse(jsonPayload);
}

const idToken = '...'; // ID Token из IDENTYX
const claims = parseJwt(idToken);

console.log('User ID:', claims.sub);
console.log('Name:', claims.name);
console.log('Email:', claims.email);
console.log('Issued at:', new Date(claims.iat * 1000));
console.log('Expires at:', new Date(claims.exp * 1000));
⚠️ Важно

Всегда проверяйте подпись JWT перед использованием данных из токена в production. Большинство OIDC библиотек делают это автоматически.

Работа с правами доступа (permissions)

IDENTYX передает права доступа пользователя через scope permissions. Права представлены в формате /объект:/действие.

Проверка прав доступа

Пример проверки прав на PHP:

<?php

function hasPermission($user_permissions, $required_permission) {
    // Точное совпадение
    if (in_array($required_permission, $user_permissions)) {
        return true;
    }
    
    // Проверка wildcard прав
    foreach ($user_permissions as $permission) {
        // Разбираем право на объект и действие
        list($obj, $action) = explode(':', $permission);
        list($req_obj, $req_action) = explode(':', $required_permission);
        
        // Проверяем wildcard
        if (($obj === '*' || $obj === $req_obj || str_ends_with($obj, '/*') && str_starts_with($req_obj, rtrim($obj, '*'))) &&
            ($action === '*' || $action === $req_action)) {
            return true;
        }
    }
    
    return false;
}

// Использование
$user_permissions = $_SESSION['user_info']['permissions'];

if (hasPermission($user_permissions, '/documents:/read')) {
    echo "Доступ к чтению документов разрешен";
} else {
    echo "Доступ запрещен";
}

Пример проверки прав на Python:

def has_permission(user_permissions, required_permission):
    """Проверяет наличие права у пользователя с учетом wildcard"""
    
    # Точное совпадение
    if required_permission in user_permissions:
        return True
    
    # Проверка wildcard
    req_obj, req_action = required_permission.split(':')
    
    for permission in user_permissions:
        obj, action = permission.split(':')
        
        # Проверяем соответствие объекта
        obj_match = (
            obj == '*' or 
            obj == req_obj or 
            (obj.endswith('/*') and req_obj.startswith(obj[:-1]))
        )
        
        # Проверяем соответствие действия
        action_match = action == '*' or action == req_action
        
        if obj_match and action_match:
            return True
    
    return False

# Использование
from flask import session, abort

@app.route('/documents')
def documents():
    user = session.get('user')
    if not user:
        abort(401)
    
    if not has_permission(user['permissions'], '/documents:/read'):
        abort(403)
    
    return "Список документов"

Получение актуальной информации о пользователе

Для получения актуальной информации о пользователе используйте эндпоинт /api/service/oidc/userinfo.

Пример запроса:

GET https://ваш-identyx-сервер/api/service/oidc/userinfo
Authorization: Bearer ваш_access_token

Пример ответа:

{
  "sub": "user_12345",
  "name": "Иванов Иван Иванович",
  "email": "ivanov@example.com",
  "email_verified": true,
  "phone": "+79001234567",
  "phone_verified": false,
  "permissions": [
    "/documents:/read",
    "/documents:/write",
    "/reports:/read"
  ]
}

Управление сессиями

Завершение сессии

Для корректного завершения сессии пользователя используйте эндпоинт /api/service/oidc/end-session.

Пример на PHP:

<?php

session_start();

// Очищаем локальную сессию
session_destroy();

// Перенаправляем на эндпоинт завершения сессии IDENTYX
$logout_url = 'https://ваш-identyx-сервер/api/service/oidc/end-session?' . http_build_query([
    'post_logout_redirect_uri' => 'https://ваше-приложение/',
    'client_id' => getenv('CLIENT_ID')
]);

header("Location: $logout_url");
exit;

Обработка истечения сессии

Access Token имеет ограниченный срок действия (по умолчанию 1 час). Когда токен истекает, вам нужно:

  1. Использовать Refresh Token для получения нового Access Token
  2. Если Refresh Token также истек — перенаправить пользователя на повторную аутентификацию

Пример обработки истечения токена:

<?php

function getValidAccessToken() {
    // Проверяем срок действия текущего токена
    if (time() >= $_SESSION['token_expires_at']) {
        // Токен истек, пытаемся обновить
        try {
            $new_tokens = refreshAccessToken($_SESSION['refresh_token']);
            $_SESSION['access_token'] = $new_tokens['access_token'];
            $_SESSION['refresh_token'] = $new_tokens['refresh_token'];
            $_SESSION['token_expires_at'] = time() + $new_tokens['expires_in'];
        } catch (Exception $e) {
            // Refresh token тоже истек, требуется повторная аутентификация
            header('Location: /login');
            exit;
        }
    }
    
    return $_SESSION['access_token'];
}
ℹ️ Backchannel Logout

Функция Backchannel Logout (автоматическое завершение сессий во всех приложениях) в настоящее время находится в разработке. Для приложений, работающих через IDENTYX Proxy, завершение сессии обрабатывается автоматически.

Лучшие практики

🔐 Безопасность
  • Храните client_secret в переменных окружения
  • Используйте HTTPS для всех запросов
  • Проверяйте подпись JWT токенов
  • Валидируйте state и nonce параметры
  • Устанавливайте короткий срок жизни сессий
⚡ Производительность
  • Кешируйте публичные ключи для проверки JWT
  • Обновляйте токены заранее, до истечения
  • Используйте connection pooling для HTTP запросов
  • Кешируйте информацию о пользователе на 5-10 минут
🛡️ Обработка ошибок
  • Обрабатывайте истечение токенов gracefully
  • Логируйте все ошибки аутентификации
  • Показывайте понятные сообщения пользователям
  • Реализуйте retry логику для сетевых ошибок
📝 Логирование
  • Логируйте успешные входы и выходы
  • Фиксируйте неудачные попытки аутентификации
  • Не логируйте токены и секреты
  • Отправляйте критичные события в журнал IDENTYX

Тестирование интеграции

Локальное тестирование

Для тестирования на локальной машине:

  1. Используйте http://localhost в redirect_uri
  2. Добавьте этот URL в список разрешенных в настройках приложения IDENTYX
  3. Если IDENTYX использует HTTPS, убедитесь что сертификат доверенный

Тестовые сценарии

Обязательно протестируйте следующие сценарии:

  • Первичный вход — пользователь впервые входит в приложение
  • Повторный вход — пользователь входит повторно (SSO)
  • Истечение Access Token — проверка обновления через Refresh Token
  • Выход из системы — корректное завершение локальной и удаленной сессий
  • Backchannel Logout — выход инициирован из другого приложения
  • Проверка прав — доступ к ресурсам согласно permissions
  • Обработка ошибок — неверный client_secret, истекший refresh_token

Типичные проблемы

Проблема Причина Решение
redirect_uri_mismatch URL перенаправления не совпадает с зарегистрированным Проверьте настройки приложения в IDENTYX и URL в коде
invalid_client Неверный client_id или client_secret Убедитесь что используете правильные учетные данные
invalid_grant Код авторизации истек или уже использован Коды одноразовые и действуют 10 минут. Получите новый код
CORS ошибки Попытка вызова OIDC эндпоинтов с клиентской стороны Все операции с токенами должны выполняться на сервере
Пустой permissions Не запрошен scope permissions Добавьте 'permissions' в список scopes при аутентификации

Миграция существующего приложения

Если у вас уже есть приложение с собственной системой аутентификации:

  1. Создайте механизм миграции — импортируйте пользователей в IDENTYX или настройте LDAP интеграцию
  2. Реализуйте параллельную работу — временно поддерживайте оба способа входа
  3. Мигрируйте пользователей постепенно — дайте время адаптироваться
  4. Обновите проверки прав — используйте permissions из IDENTYX
  5. Удалите старую систему — после полной миграции всех пользователей
✅ Готовы к интеграции?

Теперь у вас есть все необходимое для интеграции вашего приложения с IDENTYX. Начните с простого примера для вашего языка программирования и постепенно добавляйте дополнительные возможности.