사내 Works 본인인증 v2 연동 가이드

최종 수정: 2026-05-14  ·  IT Infra팀

signon_v2는 기존 v1.5 인증 서비스를 개선한 중앙 인증 서비스입니다. 대상 사이트에 Works 본인인증 버튼을 붙이는 역할은 동일하지만, 표준 OAuth 2.0 방식을 채택해 구조가 더 명확하고 보안이 강화됐습니다.

대상 사이트는 인증 화면과 Works 메시지 발송을 직접 만들지 않습니다. 사이트 담당자는 등록 정보를 IT팀에 요청하고, IT팀이 발급한 값을 사용해 버튼과 callback만 구현하면 됩니다.

v1.5 대비 개선 사항

인증 테스트 페이지

실제 사이트에 붙이기 전 v2 흐름을 테스트할 수 있습니다.

signon_v2 TEST 바로가기

1. 역할별 준비 사항

1.1. 사이트 담당자가 IT팀에 요청할 정보

본인인증 버튼을 붙일 사이트 담당자는 아래 정보를 IT Infra 팀에 전달합니다.

요청 항목예시설명
사이트명MRTG, 관리자 포털인증 화면에 표시될 서비스 이름입니다.
client_idmrtg, admin-portalURL에 들어갈 사이트 식별자입니다. 미기입 시 IT팀이 지정합니다.
callback URLhttps://site.example.com/auth/signon/callback인증 완료 후 돌아갈 대상 사이트의 서버 URL입니다. HTTPS여야 합니다.
허용 사용자 목록 선택gildong.hong, sunhee.kim인증을 허용할 Works 아이디 목록입니다. 미기입 시 Works 계정이 있는 모든 임직원에게 허용됩니다.
부가 정보 (scope) 선택profile인증 여부 외에 사용자 프로필(이름·부서)도 함께 받을지 여부입니다. 화면에 이름 표시나 부서별 권한 처리가 필요한 경우 요청합니다. 미기입 시 기본 인증 정보만 반환합니다.
허용 사용자 목록 용도: IT팀이 "이 사이트에 접근 자체를 허용할 것인가"를 결정하는 게이팅 용도입니다. 인증된 사용자 내에서의 관리자/일반사용자 구분 등 추가 권한 처리는 각 사이트에서 /token 응답의 email을 기반으로 직접 구현합니다.

1.2. IT팀이 생성해서 사이트 담당자에게 전달할 정보

IT팀은 signon_v2에 사이트를 등록한 뒤 아래 값을 사이트 담당자에게 전달합니다.

전달 항목예시사이트에서 사용하는 곳
SIGNON_CLIENT_IDmrtg/authorize URL과 /token 요청에 사용합니다.
SIGNON_CLIENT_SECRET랜덤 문자열서버에서 /token을 호출할 때만 사용합니다. 외부에 노출하면 안 됩니다.
SIGNON_REDIRECT_URIhttps://site.example.com/auth/signon/callback등록된 callback URL입니다. /authorize/token에 동일하게 사용합니다.
중요: SIGNON_CLIENT_SECRET은 서버 설정 파일 또는 환경변수에만 저장합니다. HTML, JavaScript, URL, 브라우저 저장소에 노출하면 안 됩니다.

1.3. IT팀 내부 등록 예시

아래는 IT팀이 signon_v2 운영 서버의 clients.json에 등록하는 값의 예시입니다. 사이트 담당자는 이 파일을 직접 수정하지 않습니다.

{
  "mrtg": {
    "name": "MRTG",
    "client_secret": "replace-with-long-random-secret",
    "redirect_uris": [
      "https://site.example.com/auth/signon/callback"
    ],
    "bot_id": "00000000",
    "allowed_users": ["gildong.hong", "sunhee.kim"],
    "scopes": []
  }
}
scopes: 해당 사이트가 /token 응답에서 요청할 수 있는 부가 정보 범위입니다. 기본 인증만 필요하면 []로 두고, 프로필 정보가 필요하면 ["profile"], 접근 가능 사이트 목록이 필요하면 ["profile", "sites"]를 설정합니다. IT팀에 요청 시 필요한 scope를 함께 명시하세요.
bot_id: 인증 메시지를 발송할 Works 봇의 ID입니다. IT팀이 내부적으로 관리하며, 사이트 담당자가 별도로 확인하거나 입력할 필요가 없습니다.

2. v2 인증 흐름

  1. 사용자가 대상 사이트의 Works 본인인증 버튼을 클릭합니다.
  2. 대상 사이트가 사용자를 signon_v2 /authorize로 이동시킵니다.
  3. signon_v2가 사용자의 Works 계정을 확인하고, 해당 Works 앱으로 승인 링크가 담긴 인증 메시지를 발송합니다.
  4. 사용자가 Works 앱에서 링크를 열어 승인 또는 거부합니다. 5분 안에 응답하지 않으면 자동으로 만료됩니다.
  5. signon_v2가 대상 사이트 callback URL로 code(승인) 또는 error(거부/만료)를 전달합니다.
  6. 대상 사이트 서버가 /tokencode를 제출해 사용자 정보를 받습니다. code는 2분 안에 1회만 사용 가능합니다.
  7. 대상 사이트가 자체 로그인 세션을 만들거나 추가 본인확인을 완료 처리합니다.
Works 계정: signon_v2 인증 화면에서는 Works 아이디만 입력합니다. 도메인은 서버에서 자동으로 처리됩니다.

3. 엔드포인트 정보

인증 서버 기본 URL: https://sap.mdvplab.com:5010

엔드포인트HTTP 메서드설명누가 호출하나
/authorizeGET인증 시작 화면사용자 브라우저
/tokenPOSTcallback code를 사용자 정보로 교환대상 사이트 서버
/healthzGET서비스 상태 확인관리/점검용

4. 대상 사이트 구현 1: 버튼 만들기

사이트 담당자는 IT팀이 전달한 SIGNON_CLIENT_ID, SIGNON_REDIRECT_URI를 사용해 인증 시작 URL을 만듭니다.

4.1. authorize URL 형식

https://sap.mdvplab.com:5010/authorize
  ?client_id=[SIGNON_CLIENT_ID]
  &redirect_uri=[SIGNON_REDIRECT_URI]
  &state=[CSRF 방지용 임의 문자열]
  &scope=[요청할 부가정보, 선택]

4.2. HTML 버튼 예시

<a href="AUTHORIZE_URL">Works 본인인증으로 로그인</a>

4.3. PHP 버튼 생성 예시

<?php
session_start();

$signonBaseUrl = 'https://sap.mdvplab.com:5010';
$clientId      = 'YOUR_CLIENT_ID';    // IT팀이 전달한 값
$redirectUri   = 'YOUR_REDIRECT_URI'; // IT팀에 등록한 callback URL

$state = bin2hex(random_bytes(16));
$_SESSION['signon_state'] = $state;

$loginUrl = $signonBaseUrl . '/authorize?' . http_build_query([
    'client_id'    => $clientId,
    'redirect_uri' => $redirectUri,
    'state'        => $state,
]);
?>
<a href="<?php echo htmlspecialchars($loginUrl); ?>">Works 본인인증으로 로그인</a>

5. 대상 사이트 구현 2: callback 처리

callback URL은 IT팀에 요청한 URL과 정확히 같아야 합니다. 경로, 도메인, 포트, https 여부가 다르면 차단됩니다.

5.1. 성공 callback

https://site.example.com/auth/signon/callback?code=[임시 코드]&state=[처음 보낸 state]

5.2. 실패 callback

https://site.example.com/auth/signon/callback?error=rejected&state=[처음 보낸 state]
https://site.example.com/auth/signon/callback?error=expired&state=[처음 보낸 state]
성공·실패 모두 state가 포함됩니다. signon_v2는 codeerror 응답 모두에 state를 함께 전달합니다. callback에서는 state 유무를 진입 조건으로 사용하고, error/code 분기는 state 검증 이후에 처리해야 합니다.
거부와 만료는 시스템 장애가 아닙니다. 사용자에게 "인증이 거부되었습니다" 또는 "인증 시간이 만료되었습니다"처럼 안내한 뒤 다시 시도할 수 있도록 유도하세요.

5.3. PHP callback 기본 구조

<?php
session_start();

// signon_v2는 성공(code)과 실패(error) 모두 state를 포함해 돌아옴
// → state 유무로 진입하고, 검증 후 error/code 분기 처리
if (isset($_GET['state'])) {
    if (!isset($_SESSION['signon_state']) || !hash_equals($_SESSION['signon_state'], $_GET['state'])) {
        die('인증 세션이 유효하지 않습니다. <a href="index.php">다시 시도</a>');
    }
    unset($_SESSION['signon_state']);

    if (isset($_GET['error'])) {
        $msg = ['rejected' => '인증이 거부되었습니다.', 'expired' => '인증 시간이 만료되었습니다.'][$_GET['error']] ?? '인증에 실패했습니다.';
        die(htmlspecialchars($msg) . ' <a href="index.php">다시 시도</a>');
    }

    if (isset($_GET['code'])) {
        // 6장: /token으로 code를 교환해 사용자 정보 획득
    }
}

6. 대상 사이트 구현 3: code를 사용자 정보로 교환

callback으로 받은 code는 반드시 대상 사이트 서버에서 /token으로 검증해야 합니다. code는 발급 후 2분간 유효하고 1회만 사용할 수 있습니다.

내부망 자체 서명 인증서 환경 (PHP cURL): signon_v2 서버가 사설 CA 또는 자체 서명 인증서를 사용하는 경우 /token cURL 요청 시 아래 옵션을 추가해야 합니다. 공인 인증서 환경에서는 사용하지 마세요.
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

6.1. 요청

POST https://sap.mdvplab.com:5010/token
Content-Type: application/json

{
  "client_id": "mrtg",
  "client_secret": "IT팀이 전달한 SIGNON_CLIENT_SECRET",
  "redirect_uri": "https://site.example.com/auth/signon/callback",
  "code": "CALLBACK_CODE"
}

6.2. 성공 응답 (HTTP 200)

기본 응답 (scope 없음 — 항상 반환):

{
  "active": true,
  "client_id": "mrtg",
  "email": "user@works-domain.com",
  "username": "user",
  "user_id": "75672d66-68c3-4217-17b0-03d5cdfa5106"
}

scope=profile 추가 시:

{
  "active": true,
  "client_id": "mrtg",
  "email": "user@works-domain.com",
  "username": "user",
  "user_id": "75672d66-68c3-4217-17b0-03d5cdfa5106",
  "display_name": "홍길동(Gildong)",
  "department": "IT Infra"
}

대상 사이트는 응답의 email 또는 user_id로 사용자를 식별하고 자체 세션을 생성합니다.

필드 설명:

6.3. 오류 응답

/token 요청이 실패하면 HTTP 4xx와 함께 아래 형식의 JSON이 반환됩니다.

{ "error": "오류_코드" }
오류 코드HTTP원인
invalid_client401client_id 또는 client_secret이 틀렸습니다.
invalid_request400code 또는 redirect_uri가 누락되었습니다.
invalid_grant400redirect_uri가 code 발급 시점과 다릅니다.
invalid_code400존재하지 않는 code입니다.
code_expired400code 유효 시간(2분)이 초과했습니다.
code_already_used400이미 사용된 code입니다.
rate_limited429단시간에 요청이 너무 많습니다.

7. 구현 시 자주 틀리는 부분

증상원인확인할 것
허용되지 않은 redirect_uri요청 URL과 등록 URL이 다름스킴, 도메인, 포트, 경로, 끝 슬래시까지 같은지 확인
invalid_clientclient_id 또는 client_secret 불일치IT팀이 전달한 값을 서버 설정에 넣었는지 확인
invalid_requestcode 또는 redirect_uri 누락/불일치/authorize/token의 redirect_uri가 정확히 같은지 확인
code_expiredcallback 수신 후 /token 호출이 너무 늦음callback 처리 즉시 /token을 호출하는지 확인. code 유효 시간은 2분입니다.
code_already_used같은 code를 두 번 제출callback 페이지 새로고침이나 중복 호출이 없는지 확인
state 검증 실패세션이 바뀌었거나 state 저장/검증 누락버튼 생성 시 state를 세션에 저장하고 callback에서 비교하는지 확인
Works 사용자를 찾을 수 없음Works 계정이 없거나 아이디가 다름사용자가 올바른 Works 아이디를 입력했는지 확인
뒤로가기 시 인증 메시지 재발송브라우저 POST 재전송signon_v2가 PRG 패턴으로 처리하므로 별도 조치 불필요. callback 페이지 새로고침 방지는 대상 사이트에서 직접 처리하세요.
부가 정보(display_name 등) 미수신scope 미설정 또는 미허용/authorize URL에 scope=profile을 추가하고, IT팀에 해당 scope 허용을 요청했는지 확인

8. 운영 보안 체크리스트


9. 표준 구현 사례: MRTG

사내 MRTG 모니터링 사이트가 v2 표준 구현 사례입니다. 이 가이드의 모든 예시 코드는 MRTG 구현을 기준으로 작성됐습니다.

항목
서비스 URLhttps://sap.mdvplab.com:1443/mrtg/fw/
구현 파일/var/www/html/mrtg/fw/index.php
구현 방식단일 PHP 파일에서 버튼 생성·callback 처리·세션 관리를 모두 담당