Front-end에서 사용자의 정보를 입력받아 Authentication server로 전송한다.
인증 후에는 성공적인 결과와 함께 token을 return한다. 이 token들은 subsequent requests에 사용될 것이며 credential(로그인 자격 부여)로 사용된다.
전통적으로 servers는 session table을 통해 authentication을 검증해왔다. 이것은 데이터베이스 내에 있는 literal table이며 session ID와 user ID짝을 포함한다.
매 request마다 client는 session ID를 server에 제공한다. server는 database에게 request를 요청하여 session ID가 유효한지 확인한다. 유효값임을 확인하면 작업을 계속 진행할 수 있다.
하지만 microservices architecture를 사용할때는 전 세계의 다른 parts에 위치할 수도 있는 전체 시스템에 걸쳐서 그 상태를 유지해야 하는 수백개의 서비스가 필요할 수 있다.
이것은 유지보수에 문제가 된다. request를 생성하고 session을 체크하는데 레이턴시와 시간이 소요된다. 또한 sessions은 stack의 다른 부분을 지나면서 바뀔수도 있다. 또한 그 변화가 전파(propagate) 되기까지 시간이 걸린다. 이대신 우리는 stateless(무상태)한 무언가를 원한다.
그 말인 즉슨 우리의 서버는 token이 유효하고 작동을 하는 것을 알기만 하면 된다는 것이다.
JSON web token은 본질적으로 stateless하다. token이 front-end와 server로 전송되면 server는 public key를 authentification service로 부터 한번만 가져오면(fetch) 된다.
이 authentication key는 API server에 저장되고 해당 JWT가 valid하며 누구인지 신뢰할 수 있다는 것을 검증하도록 한다.
Statelessness는 scalability(확장성)의 문제도 해결한다. 만약 많은 요구(tremendous aomount of demand)가 필요한 API server를 가지고 있다고 가정해보자. 이 경우에는 우리는 같은 서비스를 통해 여러개의 instances를 spinning up(전송?)하게 될 것이다. 우리의 JWT는 stack 내에 있는 이 server들 중 어떤 것이든 hit할 수 있다. Ststeless하므로, 각 서버는 제공된 ID를 신뢰할 수 있다.
앞서 언급했던 것과 같이 statelessness는 마이크로 서비스 아키텍처에서 놀라운 효과를 발휘한다. JWT가 어디서 끝난 것에 관계없이 각각의 서비스들은 제공된 authentication을 신뢰할 수 있다.
Web token은 겉보기에는 의미없는 문자열들로 보인다. 하지만 그 안에 숨겨진 것은 꽤 직관적인 구조를 띄고 있다. 이것은 3개의 메인 파트로 나뉘며 header.payload.signature
로 구성된다. 이 세부분이 함께 모여 JWT에 포함된 정보들은 consistent하며 정보가 바뀌지 않았다는 것을 검증할 수 있다.
JWT의 세 개의 parts가 함께 작용하여 'who is making the request?', 'Do we trust that the information is indeed valid?`의 두 질문에 답할 수 있다.
보통 header는 algorithm에 대한 것을 담고 있다.
base-64 decoding후에 우리는 user object를 얻게 된다. 이것은 user에 대한 정보를 담고 있다. 여기서 가장 중요한 점은 이 정보들은 딱히 비밀이 아니라는 점이다. JWT base-64 encoding은 부가적인 정보 없이 매우 쉽게 디코드될 수 있기 때문에 JWT를 가지고 있는 누구에게나 쉽게 접근이 가능한 data이다. 그렇기때문에 비밀번호같은 민감한 정보는 절대 저장되어서는 안된다.
Payload으로 `who is making the request?'에 대한 답을 얻을 수 있다. 하지만 우린 아직 'Do we trust this information?'에 대한 질문이 남았다.
base-64 encoding scheme는 매우 사용하기가 쉽기 때문에 누구나 JWT token을 생성할 수 있다. JSON object를 만들고 base-64 encoing을 거쳐 우리의 server에 token으로 보낼 수 있는 것이다.
우리는 이제 이 JWT가 진짜 우리가 신뢰하는 system에서 생성되었는지, JWT가 request를 만든 개인의 authentic identity를 갖고 있는지에 대한 답을 얻어야한다.
우리는 이제 어떻게 JWT가 authentic하며 JWT가 변경되지 않았는지를 신뢰할 수 있는지를 이해해야 한다.
Signature의 목적은 JWT의 정보가 변경되지 않았는지 또한 신뢰할만한 source에서 왔는지를 확인하는 것이다. 이 목적을 달성하기 위해서는 signature를 생성하는 function이 필요하다.
Secret은 우리의 authentification service에 있는 string이며 server에서 JWT를 검증하기 위해 사용될 것이다. Secret이 제 3자에게 알려지지 않았다면 그들의 payload와 header로 정보를 sign할 수 없다.
만약 header나 layload가 바뀌고 secret은 그대로라면, signature은 바뀌게 된다. 따라서 Auth 서비스에 서명된 JWT가 우리가 사용하는 API 서버에 할당되었을 때 동일한 서명을 포함하지 않는다면, 우리는 데이터가 전송 중에 변경되었다는 것을 안다.
Header로 돌아가서 생각해보면 우리는 HMACSHA256이라는 알고리즘을 포함했었다. 이것은 signing method를 수행하는데 보편적으로 사용되는 알고리즘이다.
필수적으로 우리는 우리의 header와 payload를 base-64 encode한후 점을 이용해서 붙여주고 secret을 넣어서 제공된 secret, payload, header에만 유효한 signature string output을 생성한다.
동일한 알고리즘이 JWT를 검증하는 데에도 사용된다. JWT token의 header와 payload를 가지고 같은 secret을 사용해서 이 알고리즘으로부터 나온 signature를 비교한다.
import jwt
import base64
# Init our data
payload = {'park':'madsion square'}
algo = 'HS256'
secret = 'learning'
# Encode a JWT
encoded_jwt = jwt.encode(payload, secret, algorithm=algo)
# Decode a JWT
cdcoded_jwt = jwt.decode(encoded_jwt, secret, verify=True)
# Decode with Simple Base64 Encoding
decoded_base64 = base64.b64decode(str(encoded_jwt).split(".")[1]+"==")
import sys
!{sys.executable} -m pip install python-jose
import json
from jose import jwt
from urllib.request import urlopen
# Config
# Update this to reflect your auth0 account
AUTH0_DOMAIN = 'fsnd.auth0.com'
ALGORITHMS = ['RS256']
API_AUDIENCE = 'image'
'''
AuthError Exception
A standardized way to communicate auth failure modes
'''
class AuthError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code
# PASTE YOUR OWN TOKEN HERE
# MAKE SURE THIS IS A VALID AUTH0 TOKEN FROM THE LOGIN FLOW
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik56Z3lSVFZEUmpKQ1FrUkJSRGN3TjBReFJUQTFPVUl5UlRORk9EQXhPVGMwTmpjNU9USkVPQSJ9.eyJpc3MiOiJodHRwczovL2ZzbmQuYXV0aDAuY29tLyIsInN1YiI6ImF1dGgwfDVkMDNkM2U2NzI2YjhmMGNiNGJmNzFjOSIsImF1ZCI6ImltYWdlIiwiaWF0IjoxNTYwNTU2MTc0LCJleHAiOjE1NjA1NjMzNzQsImF6cCI6ImtpNEI2alprdUpkODdicEIyTXc4emRrajFsM29mcHpqIiwic2NvcGUiOiIiLCJwZXJtaXNzaW9ucyI6WyJnZXQ6aW1hZ2VzIiwicG9zdDppbWFnZXMiXX0.ENxNT1lo_sX9rpgmGJmiu14lugmYXqb8siJwC1nPuGSb_ycK02KyS5IkA-YkhySMBcDD5IJfawPkJNmJPtUAB1wYVP8hlNsBuvLjtYxzH_VHNeXzVXWQvM7RiuPwrmWJmJN2onmZPh3bjiUZxvyAp0Yp0Rvm54SsiDjO_Dj1Qx-Az_Zjo-mY2ECfFgAo0ifnqDMIgE5YDZ3uOzMni4oEU5Ok-TrQOSwyfJyUC1KQ7ubQ-Bnbh-0Aii9UK9R4JBH7iIMva8_edQkgR4MuRXatYhsqvHsxQ2Iv5rjMmTAmjknsYWE5VYrzafRGVigbPD9A6ELEnyjADBQ9vMtSdPQe2w"
## Auth Header
def verify_decode_jwt(token):
# GET THE PUBLIC KEY FROM AUTH0
jsonurl = urlopen(f'https://{AUTH0_DOMAIN}/.well-known/jwks.json)
jwks = json.loads(jsonurl.read())
# jwks는 key들을 return하며 그 중 kid는 header에서 사용된다.
# GET THE DATA IN THE HEADER
unverified_haeder = jwt.get_unverified_header(token)
# header를 unpack하여 포함된 kid가 위의 kid와 동일한지 확인한다.
# CHOOSE OUR KEY
rsa_key = {}
if 'kid' not in unverified_header:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization malformed.'
}, 401)
for key in jwks['keys']:
if key['kid'] == unverified_header['kid']:
rsa_key = {
'kty': key['kty'],
'kid': key['kid'],
'use': key['use'],
'n': key['n'],
'e': key['e']
}
if rsa_key:
try:
# USE THE KEY TO VALIDATE THE JWT
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer='https://'+AUTH0_DOMAIN+'/'
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError({
'code': 'token_expired',
'description': 'Token expired'
}, 401)
except jwt.JWTClaimsError:
raise AuthError({
'code': 'invalid_claims',
'description': 'Incorrect claims. Please, check the audience and issuer.'
}, 401)
except Exception:
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to parse authentication token.'
}, 400)
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to find the appropriate key.'
}, 400)