Code Walkthrough
Client Side
When setting up OAuth2 from client side, you have two options:
- Build the entire flow yourself
- Make a request for building the authorization URL
- Handling the Webview with the created URL
- Getting the
code
after authenticating with ZEBEDE - Making another request for getting the token
- Make use of some of the available OAuth libraries available for the language you are working on, which would handle the initial steps of the flow
- Install and set up configs accordingly to the library
- If PKCE is available on the library you might receive the
code_verifier
from the response, after authenticating. - If PKCE is not available, you might need to build the code_challenge and code_verifier yourself.
- If PKCE is available on the library you might receive the
- Getting
code
as response - Making the request for getting the token
- Install and set up configs accordingly to the library
If you chose the second option, there are open-source libraries for handling OAuth2 on Mobile using AppAuth (iOS and Android), as well as for React Native using react-native-app-auth.
Server Side
Node.js
This flow go over all the steps for Logging in with ZEBEDEE, from Building the authorization URL, with proper PKCE Keys, to getting the accessToken and calling ZBD API.
If you are using a library that provides PKCE guidelines, it will most likely be able to handle the initial flow (building auth URL and getting the code) just fine. In this case, feel free to jump to the Token Section.
Building Authorization URL
In case you are setting up the PKCE flow yourself (no usage of libraries), we will need to setup the code_challenge
from a code_verifier
which will be used later on, on token request.
const crypto = require('crypto');
import OAuth from 'oauth';
function sha256(buffer) {
return crypto.createHash('sha256').update(buffer).digest();
}
function base64URLEncode(str) {
return str
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
export function GeneratePKCE() {
const verifier = base64URLEncode(crypto.randomBytes(32));
if (verifier) {
const challenge = base64URLEncode(sha256(verifier));
return { challenge, verifier };
}
}
const createZBDOauth = () => {
const { OAuth2 } = OAuth;
const authorizeUrl = 'oauth2/authorize';
const tokenPath = 'oauth2/token';
const oauth2 = new OAuth2(
ZBD_CONSUMER_KEY, // CLient ID
ZBD_CONSUMER_SECRET, // Client Secret
ZEBEDEE_API_URL, // ZBD API URL: https://api.zebedee.io/v0/
authorizeUrl, // Authorization URL: oauth2/authorize/
tokenPath, // Token Path: oauth2/token
null,
);
return oauth2;
};
// Called by the app to get a url it will open in a webview/or browser
export const getZBDLoginUrl = async (userId: string) => {
// generate the PKCE key
const { verifier, challenge } = GeneratePKCE();
// save the verifier/key to the user object so we can access it when validating in the callback
await User.findOneAndUpdate({ userId }, { oauthVerifier: verifier });
const scope = 'user';
const state = userId;
const suffix = `&response_type=code&code_challenge=${challenge}&code_challenge_method=S256&state=${state}`;
const oauth2 = createZBDOauth();
// use NPM module to create the url
const res = await oauth2.getAuthorizeUrl({
redirect_uri: ZBD_REDIRECT_URL,
scope,
});
// npm module doesnt support PKCE so append the url with the code_challenge info
const url = res + suffix;
return url;
};
Code explanation
getZBDLoginUrl
function- In this function, we initially Generate PKCE keys:
verifier
andcode_challenge
- Then,
findOneAndUpdate
represents an generic ORM call for updating the User model, on the database. This way we can, afterwards, retrieve that value for getting the token. - We then Build the URL suffix
- Url prefix and generated from
createZBDOauth
, which usesoauth
library to do so
- In this function, we initially Generate PKCE keys:
generatePKCE
- This is where we setup PKCE Keys.
- initially we set the code verifier:
const verifier = base64URLEncode(crypto.randomBytes(32));
- From the code verifier, the code_challenge is created:
const challenge = base64URLEncode(sha256(verifier));
- initially we set the code verifier:
- This is where we setup PKCE Keys.
When logging in using the provided URL, you will get a response including state
and code
which should be user now for getting the token, and user data:
Getting the token and accessing ZBD API
// called by ZBD oauth on login
export const zbdLoginCallback = async (payload: Object) => {
const { code, state } = payload;
try {
const user = await User.findOne({ userId: state });
const { oauthVerifier } = user;
// get the verifier generated by the PKCE script
const res = await getAccessToken({
code,
client_secret: ZBD_CONSUMER_SECRET,
client_id: ZBD_CONSUMER_KEY,
code_verifier: oauthVerifier,
grant_type: 'authorization_code',
redirect_uri: ZBD_REDIRECT_URL,
});
const { access_token } = res.data;
// get user data now we have the access token
const response = await getUserData(access_token);
const { data } = response;
const userData = data.data;
const { gamertag, email, isVerified, id } = userData;
if (!gamertag || !id || !email) {
throw new Error('gamer tag not found');
}
// save the details to the user object so we now know their email, verification etc
user.gamerTag = gamertag;
user.gamerTagId = id;
user.email = email;
user.isZBDVerified = isVerified;
user.oauthLoginDate = new Date();
await user.save();
// show a webpage that will redirect back to the app app by calling the app schema url
return {
html: path.resolve(`${__dirname}/../callback/oauth-callback.html`),
};
} catch (e) {
console.error(e);
return {
html: path.resolve(`${__dirname}/../callback/oauth-callback-error.html`),
};
}
};
Code explanation
Once the user is successfully authenticated on ZEBEDEE Client, there is a redirect back to your app, sending code
and state
as query params.
Those values are send as payload on this next request:
- First of all, remember we registered the verifier on the User (generic ORM update)? now we retrieve that value:
const user = await User.findOne({ userId: state });
- With the proper verifier, we can now make a post request to the token endpoint, and we should have the token returned.
const res = await getAccessToken({…body})
export const getAccessToken = async (payload: Object) => {
const response = await axios({
method: 'POST',
data: payload,
url: `https://api.zebedee.io/v0/oauth2/token`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
return response;
}; - Now, by adding the accessToken to request headers, we can fetch data from ZBD API
const response = await getUserData(access_token);
export const getUserData = async (accessToken: string) => {
const response = await axios({
method: 'GET',
url: `https://api.zebedee.io/v0/oauth2/user`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
Authorization: `Bearer ${accessToken}`,
},
});
return response;
};
You are now able to proceed with your login logic and send the user data accordingly ⚡
Client Examples
React Native: https://github.com/zebedeeio/login-zbd-react-native-example
Express: https://github.com/zebedeeio/login-zebedee-express-example