4. Handle Redirect URI
Introduction
You should now have the Cyberus Key Widget embedded in your front-end. Now it’s time to provide the endpoint which you set as your Redirect URI (see “Register your account”). This endpoint will be called when a user has been authorized.
Below you’ll find the list of libraries you can use to handle the Cyberus Key authorization process.
PHP
The Cyberus Key PHP integration library can be found on GitHub and can be installed with Composer. The authenticate method must be called in your Redirect URI’s handler.
Python
The Cyberus Key Python integration library can be found on GitHub and can be installed with pip. Follow the usage example in your Redirect URI’s handler.
NodeJS
For Node JS integration you can use one of the available OAuth2 libraries. The following example uses passport-oauth2.
passport.use('oauth2', new OAuth2Strategy({
authorizationURL: 'https://api.cyberuskey.com/api/v2/authenticate',
tokenURL: 'https://api.cyberuskey.com/api/v2/tokens',
clientID: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
callbackURL: 'http://your-api.com/auth/callback',
customHeaders: {'Authorization': 'Basic ' + Buffer.from(clientID + ':' + clientSecret).toString('base64')},
},
function(accessToken, refreshToken, profile, done) {
}
));
If you want retreive information about authenticated user make a call to https://api.cyberuskey.com/api/v2/userinfo with the Authorization header and access token value:{Authorization: 'Bearer ' + accesToken}
.NET
We don’t currently have a library for .Net technologies. Please see the “Custom implementation” section below.
Custom implementation
Don’t worry! We know that we don’t directly support every back-end technology, but it’s easy to integrate your back-end with us! It only requires implementing a handler for the Redirect URI endpoint.
The Redirect URI is called after a user has been authorized, but your back-end must authorize them as well.
Steps what to do
How to do that? The steps are:
- Extract the Authorization Code from the query string (called code).
- Optional. If you use the state parameter (when embedding the Cyberus Key Widget) compare its value with the one in the query string.
- Call the Token Request (Cyberus Key API) according to our documentation. Set the Authorization header (HTTP Basic Auth) to the Base64-encoded value of Client ID:Client Secret Key (see Register your account if you don’t have a Client ID and Client Secret Key).
- In the successful response from Token Request, you will receive two JWT tokens: ID Token and Access Token.
- Validate ID Token. Use the Cyberus Key Public Key (RS256) to verify that the token originated from the Cyberus servers. You can also validate the JWT claims.
If you passed nonce parameter, check it here – it’s part of the ID Token. - That’s it! The ID Token will contain info about the user (like email).
Python example
class AuthorizationCallbackHandler(tornado.web.RequestHandler):
def get(self):
error = self.get_argument('error', None)
error_description = self.get_argument('error_description', None)
if error:
url=f'/login_page&error={error}&error_description={error_description}'
self.redirect(url)
return
# Optional. Checking the state before making the Token Request.
state = self.get_argument('state', None)
original_state = self.get_secure_cookie('state').decode("utf-8")
if original_state != state:
self.redirect(f'/login_page&error=InvalidState')
return
# Making the Token Request.
data = {
'grant_type': 'authorization_code',
'code': self.get_argument('code'),
'redirect_uri': f'{options.host_url}demo/authorization-callback'
}
token=base64.b64encode(str.encode(f"{CLIENT_ID}:{CLIENT_SECRET}"))
headers = {
'Authorization': f'Basic {token.decode("utf-8")}'
}
url = f'{options.auth_server_url}/api/v2/tokens'
token_response = requests.post(url, data=data, headers=headers)
token_body = token_response.json()
id_token = token_body['id_token']
access_token = token_body['access_token']
# Decoding the ID Token. Checking the auditory.
id_data = jwt.decode(id_token, CYBERUSKEY_PUBLIC_KEY, algorithms=["RS256"], audience=CLIENT_ID)
# Checking the iss claim, you can check also exp and iat.
if id_data.get('iss') != CLIENT_DOMAIN:
self.redirect(f'/login_page&error=Unauthorized')
return
# Optional. Checking the nonce to prevent reply attacks.
nonce = id_data.get('nonce')
original_nonce = self.get_secure_cookie('nonce').decode("utf-8")
if original_nonce != nonce:
self.redirect(f'/login_page&error=InvalidNonce')
return
# Optional. Decoding the Access Token.
access_token_audience = [
'https://api.cyberuskey.com/api/v2/userinfo',
'https://api.cyberuskey.com/api/v2/logins',
'https://api.cyberuskey.com/api/v2/clients',
'https://api.cyberuskey.com/api/v2/login-stats',
'https://api.cyberuskey.com/api/v2/transactions'
]
access_data = jwt.decode(access_token, CYBERUSKEY_PUBLIC_KEY, algorithms=["RS256"], audience=access_token_audience)
if access_data.get('azp') != CLIENT_ID:
self.redirect(f'/login_page&error=InvalidAzp')
return
# Optional. Checking the at_hash and c_hash values.
original_at_hash = id_data.get('at_hash')
original_c_hash = id_data.get('c_hash')
if original_at_hash:
computed_at_hash = self._compute_claim_hash(access_token)
if original_at_hash != computed_at_hash:
self.redirect(f'/login_page&error=InvalidAtHash')
return
if original_c_hash:
computed_c_hash = self._compute_claim_hash(self.get_argument('code'))
if original_c_hash != computed_c_hash:
self.redirect(f'/login_page&error=InvalidCHash')
return
# Creating the application session.
session_id = self.get_cookie(options.cookie_name)
session = self._session_store.load_session(session_id)
# scope is 'openid email profile'
session['user'] = {
'first_name': id_data.get('given_name'),
'last_name': id_data.get('family_name'),
'email': id_data.get('email'),
'openid_identifier': id_data.get('sub'),
'exp': id_data.get('exp'),
'access_token': access_token
}
self._session_store.save_session(session)
self.redirect(f'/post-login-page')
Cyberus Key Public Key
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHElKnuERpCN/WcD6RtS9rKhJODM
Idr2Y1yFrS255cOaG10CLwFPhSVK5z4HQv5/VN3GB2Ft+fbu9OZRTqdA4lHo0PB3
Kaj3yByDUdIoTHd4RmZMLSFVHKR0KAW193nI7s/pzeqDL0oFpHnRNZGUqhRbm2UK
fHHDWKkTn/iGIV7XAgMBAAE=
-----END PUBLIC KEY-----
CSRF/XSRF mitigation (state parameter)
It is highly recommended to use the state parameter. The state claim is bound to the Authentication Request with its response. There are a few ways of doing this, but the easiest is to use a secure cookie (the method specified in the OpenID specs). It is also recommended to make the cookie HTTP-only.
You have to pass the state value to the Cyberus Key Widget, so if you are using server-side rendering, then you can embed the value in the template and set a secure and HTTP only cookie. Otherwise, then you should retrieve the state value from your back-end via a HTTP request, which creates the cookie with proper flags.
The goal is to have the state value in a secure cookie which allows you to check it on the back-channel, which can be compared to the state value you receive in the query string of the request to your Redirect URI endpoint.
See the example Python example above.
Mitigate reply attacks (nonce parameter)
The nonce parameter is used to mitigate replay attacks. Everything about this parameter is the same as for the state. You have to pass it to the Cyberus Key Widget, set a secure and HTTP only cookie with this value and check in in your back-end. The only difference is that the nonce is checked AFTER the Token Request because its value is contained in the ID Token.
See the example Python example above.
Claim: at_hash
With this ID Token‘s claim, you can check the correctness of the Access Token. The Access Token value is encoded with the SHA-256 algorithm, then the first 128 bits are taken and base64 encoded (respecting the URL encoding).
See the example Python example above.
Claim: c_hash
This claim is also contained in the ID Token and it’s computed the same way as at_hash, but the value encoded is the Authorization Code.
See the example Python example above.