Understand OAuth Authorization Code Grant Type
Fork this project on GitHub
https://github.com/mifrazmurthaja/oauth2.0-basic
Prerequisites
- Refer Understand What OAuth is and How it Works prior to understand the application!
- This application is developed using Spring Boot Framework.
Project Structure
- The project has 3 applications
- Resource Server of wowelephant.lk - Runs on port 8082
- localhost:8082
- Authorization Server of wowelephant.lk - Runs on port 8081
- localhost:8081
- Client Application - Walapalam - Runs on port 9999
- localhost:9999
- Project Structure is as follows
- Resource Server of wowelephant.lk
ResourceServer.java
The main class of Resource Server.
The main class of Resource Server.
User.java
The user class consists of attributes of the user and get-set methods.
The user class consists of attributes of the user and get-set methods.
UserData.java
Functions related to the users such as, addUser(), updateUser(), viewUser() and etc. In this case, we use files to store the data instead of Database.
application.yml
Configurations such as port number and the details of the Authorization Server.
Users.dat
The file where all the user details are stored.
Each of following functions has a scope, path and a request method. To access any of the functions, it requires a valid access token with the given request method (GET, POST, etc)
For example,
localhost:8082/friends requires an access token with the user_friend scope and it can be accessed only through GET method.
localhost:8082 (Root - /) will return “Welcome” if it is accessed through GET method with a valid token consists of public_read scope.
private String message = "Welcome!";
@PreAuthorize("#oauth2.hasScope('public_read')")
@RequestMapping(value = "/", method = RequestMethod.GET)
public Map<String, String> home() {
return Collections.singletonMap("message", message);
}
@PreAuthorize("#oauth2.hasScope('public_write')")
@RequestMapping(value = "/", method = RequestMethod.POST)
public void updateMessage(@RequestBody String message) {
this.message = message;
}
@PreAuthorize("#oauth2.hasScope('user_read')")
@RequestMapping(value = "/user", method = RequestMethod.GET)
public Map<String, String> user(Principal user) {
return Collections.singletonMap("message", "user is: " + user.toString());
}
UserData userData = new UserData();
@PreAuthorize("#oauth2.hasScope('user_friend')")
@RequestMapping(value = "/friends", method = RequestMethod.GET)
public List<User> getUsers(){
return userData.getAllUsers();
}
- Authorization Server of wowelephant.lk
AuthorizationServer.java
The main class of Authorization Server.
The main class of Authorization Server.
OAuthConfig.java
Configurations of OAuth framework such as Client IDs, Client Secrets, Token Validity periods, scopes and etc.
SecurityConfig.java
Defines how the Authorization Server can be accessed such as login page and user credentials. (Not the client credentials – Credentials of real users whose data is stored on the resource server)
application.properties
Configurations such as port number and the server context path.
jwt.jks
The key file. Password for the key is stored in the application.properties file.
The client details of walapalam application are stored in the following function. Since this application does not use a database, all the details are hardcoded. We can store multiple client details using and() method. The client 1000123456 can only request user_read or user_friend or both from users. And the authorization code will be sent to the redirection URI defined below. The authorization flow grant type used in this application is authorization code grant type.
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("1000123456")
.authorizedGrantTypes("authorization_code")
.scopes("user_read","user_friend")
.redirectUris("http://localhost:9999/oauth/access/?key=value")
.secret("mAac87WQq")
.accessTokenValiditySeconds(5184000);
}
Below function defines the user credentials whose resource is stored on the resource server. (Hardcoded)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.parentAuthenticationManager(authenticationManager)
.inMemoryAuthentication()
.withUser("john")
.password("123")
.roles("USER");
}
- Client Application - Walapalam
App.java
The main class of Client Application - Walapalam.
The main class of Client Application - Walapalam.
AppController.java
Define paths for the HTML static pages which are store on resources/static folder.
AppRestController.java
Defines paths for the functions.
application.yml
Configurations such as the port number.
index.html
Homepage (Login page) of the walapalam application. The path for this file is defined in AppController.java – Root path (/) – Hence, localhost:9999/ returns this page.
Path for the index.html is defined as root in AppController.java class
@RequestMapping("/")
public String index() {
return "index.html";
}
A link button – Login with Wowelephant, which redirects the user to localhost:9999/login-wowelephant
<a href="/login-wowelephant" class="btn button btn-primary a-btn-slide-text">
<span><strong>Login with WowElephant</strong></span>
</a>
If anyone accesses the /login-wowelephant path, following function will be triggered and will be redirected to the URL defined below. If you have a look at the URL, it clearly shows that
· Port number is 8081. ie, the Authorization Server.
· Client ID of the walapalam application.
· Scopes (Separated by spaces – encoded value: %20)
· The redirect URI. The endpoint, the authorization code will be sent to.
o If you have look at the redirect URI, it is localhost:9999 and that is the Client App. Hence, the authorization code will be sent to the client app on /oauth/access path. Before sending the code to the client app, the user will be asked to approve the request by User Consent Page.
· Response type – Authorization Code.
@RequestMapping(value = "/login-wowelephant", method=RequestMethod.GET)
public RedirectView processForm1() {
RedirectView redirectView = new RedirectView();
redirectView.setUrl("http://localhost:8081/auth/oauth/authorize?response_type=code&client_id=1000123456&redirect_url=http://localhost:9999/oauth/access?key=value&scope=user_friend%20user_read");
return redirectView;
}
When the authorization code received to /oauth/access, the following method will be executed.
Now, we do all the rest of steps on this method itself. (From step 5 – 10)
@RequestMapping(value = "/oauth/access", method = RequestMethod.GET)
public String handleResponse(ModelMap model, @RequestParam(value = "code",required=true) String authCode) {
String accessToken = getAccessToken(authCode); //Get the access token from the authorization server
return getResource(accessToken); //Get the resource from the resource server using the access token
}
It has 2 functions,
· getAccessToken() – Get the access token from the authorization server by providing the received authorization code. (Steps 5 & 6)
o Method should be POST
o Mention the Grant Type and the Authorization Code in the body.
o Set the Authorization Header with value of encoded value in the form of
Basic Client_ID:Client_Secret
· getResource() – Get the resource of the user from the resource server by providing the access token.
o Method can be any.
o Access the required path depending on the resource to be accessed.
o Set the Authorization Header with value in the form of
Bearer Access_Token
public String getAccessToken(String authCode)
{
String auth_url = "http://localhost:8081/auth/oauth/token";
String POST_PARAMS = "grant_type=authorization_code&code="+authCode;
String CLIENT_ID = "1000123456";
String CLIENT_SECRET = "mAac87WQq";
String accessToken = "";
try {
URL obj = new URL(auth_url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
String cred = CLIENT_ID+":"+CLIENT_SECRET;
byte[] encodedCred = Base64.getEncoder().encode(cred.getBytes());
//Set Headers
con.setRequestProperty("content-type", "application/x-www-form-urlencoded");
con.setRequestProperty("Authorization", "Basic " + new String(encodedCred));
//Set Body
con.setDoOutput(true);
OutputStream os = con.getOutputStream();
os.write(POST_PARAMS.getBytes());
os.flush();
os.close();
//Execute and get the response
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK)//success
{
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null)
{
response.append(inputLine);
}
in.close();
//Convert the response to a json Object
JSONObject jsonObj = new JSONObject(response.toString());
//Get the access token from json object
accessToken = jsonObj.getString("access_token");
}
else {
System.out.println("Error : " + responseCode);
}
}
catch (Exception ex)
{
System.out.println(ex);
}
return accessToken;
}
public String getResource(String accessToken)
{
try {
String ResourceUrl = "http://localhost:8082/friends";
URL obj = new URL(ResourceUrl);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
//Set Header
con.setRequestProperty("Authorization", "Bearer "+accessToken);
//Execute and get the response
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) //success
{
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer ResourceResponse = new StringBuffer();
while ((inputLine = in.readLine()) != null)
{
ResourceResponse.append(inputLine);
}
in.close();
//Convert the ouput to json array
JSONArray jsonArray = new JSONArray(ResourceResponse.toString());
//Prepare the ouput from the json array
String output = "<html><body><h2>Friends List</h2><table width=50% style=\"text-align:center\">";
output += "<tr><th>ID</th><th>Name</th></tr>";
for (int i = 0; i < jsonArray.length(); i++)
{
JSONObject explrObject = jsonArray.getJSONObject(i);
output += "<tr>";
output += "<td>" + explrObject.getString("id") + "</td>";
output += "<td>" + explrObject.getString("name") + "</td>";
output += "<tr>";
}
output += "</table></body></html>";
return output;
}
else {
System.out.println("Error : " + responseCode);
}
}
catch (Exception ex)
{
System.out.println(ex);
}
return null;
}
·
Resource Server cannot be accessed directly.
·
User clicks
on Login with WowElephant
·
The user will be
redirected to the login page of the Authorization Server since the user is not
logged in. It will be requested each time since this application mainly
focuses on the OAuth protocol hence sessions are not used.
·
User Consent
Page.
·
Client app
will be provided with an Authorization Code (Have a look at the below URL)
after the user authorized the above request. After that, client app continues
with the rest of the step to get the resource of the user displayed below.
How to run the project?
Open servers and application on 3 different terminals and execute the command,
mvn spring-boot:run
While the servers are running, open the browser and go to localhost:9999
To stop the servers, enter ctrl + C
https://github.com/mifrazmurthaja/oauth2.0-basic
That's all folks!