dev_note/TIL
OAuth2 State, Nonce 값 만들기
jjoylee
2022. 4. 25. 14:42
📌 정의
보통 SNS로그인을 할 때 AuthorizationEndpoint에 요청할 때 다음과 같은 두 값을 보낸다.
state : CSRF 대책으로서 동일한 세션임을 확인하기 위한 값. 각 요청에 대해 애플리케이션 측에서 생성한 임의의 문자열을 지정한다.
nonce : 리플레이 공격 대책을 위한 파라미터. 각 요청에 대해 임의의 문자열을 지정한다. 지정한 nonce 값은 ID Token에 포함되어 반환된다.
이를 C# OAuth2 개발할 때 다음과 같이 구현할 수 있다.
- 임의 바이트 값의
nonce
를 생성하고 이를 쿠키에 저장한다. (GenerateCorrelationId
) - nonce와 다른 정보를 암호화 해서
state
를 만들고 이를 쿼리 스트링으로 전달한다. - 응답으로 받은 state 쿼리 스트링 파라미터를 복호화해서 cookie에 저장된 값과 일치하는지 검증한다. (
ValidateCorrelationId
)
protected void GenerateCorrelationId(AuthenticationProperties properties)
{
if (properties == null)
{
throw new ArgumentNullException("properties");
}
string correlationKey = Constants.CorrelationPrefix +
BaseOptions.AuthenticationType;
var nonceBytes = new byte[32];
Random.GetBytes(nonceBytes);
string correlationId = TextEncodings.Base64Url.Encode(nonceBytes);
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
properties.Dictionary[correlationKey] = correlationId;
Response.Cookies.Append(correlationKey, correlationId, cookieOptions);
}
protected bool ValidateCorrelationId(AuthenticationProperties properties,
ILogger logger)
{
if (properties == null)
{
throw new ArgumentNullException("properties");
}
string correlationKey = Constants.CorrelationPrefix +
BaseOptions.AuthenticationType;
string correlationCookie = Request.Cookies[correlationKey];
if (string.IsNullOrWhiteSpace(correlationCookie))
{
logger.WriteWarning("{0} cookie not found.", correlationKey);
return false;
}
var cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
Response.Cookies.Delete(correlationKey, cookieOptions);
string correlationExtra;
if (!properties.Dictionary.TryGetValue(
correlationKey,
out correlationExtra))
{
logger.WriteWarning("{0} state property not found.", correlationKey);
return false;
}
properties.Dictionary.Remove(correlationKey);
if (!string.Equals(correlationCookie, correlationExtra, StringComparison.Ordinal))
{
logger.WriteWarning("{0} correlation cookie and state property mismatch.",
correlationKey);
return false;
}
return true;
}
// REQUEST
// nonce 생성
GenerateCorrelationId(properties);
// state 생성
var state = Options.StateDataFormat.Protect(properties);
// RESPONSE
// state 값 쿼리스트링에서 가져오기
var state = GetValue(query, "state");
properties = Options.StateDataFormat.Unprotect(state);
if (properties == null)
{
return null;
}
// 검증
if (!ValidateCorrelationId(properties, _logger))
{
return new AuthenticationTicket(null, properties);
}