Introduction
Most companies and organizations have employee and customer databases. When they adopt ManualWorks, they face the inconvenience of managing some of that data twice. To solve this, ManualWorks can manage users with OAuth 2.0. OAuth 2.0 is also used to integrate ManualWorks with an in-house authentication system through SSO (Single Sign-On). If OAuth 2.0 is hard to apply in your environment, you can also handle login with JSP.
What Is OAuth 2.0?
OAuth 2.0 is an open protocol that lets applications control authentication and authorization securely. Its main components are as follows.
- Authorization server
The server that handles user authentication, such as login. You add the functions required by OAuth to a website that can access your employee or customer database.
- Client (ManualWorks)
The service that users can use after they log in through the authorization server. ManualWorks is the client.
- Web browser
Connects the authorization server and ManualWorks through web browser redirects.
OAuth describes the authorization server and the resource server separately. The resource server provides resources such as user profiles. When you apply OAuth to ManualWorks, you do not need to separate them, so this chapter assumes that the authorization server handles both authentication and resources.
With OAuth 2.0, ManualWorks authenticates users as follows.
1
The web browser requests ManualWorks.
Publicly published documents can be accessed directly.
2
If the user is not authenticated, ManualWorks redirects the web browser to the authorization server with the following query string parameters.
redirect_uriThe ManualWorks address to redirect the web browser to after successful authentication. It is the base URL followed by
/r/oauth/auth.stateA safeguard against duplicate calls. It is sent back as a query string parameter after successful authentication.
3
When authentication succeeds, the authorization server redirects to the redirect_uri received from ManualWorks with code and state in the query string.
For security, you can also redirect to a predefined address (http://127.0.0.1:1975/r/oauth/auth).
4
ManualWorks requests a token from the authorization server with code as a parameter.
ManualWorks calls the authorization server directly, not through the web browser.
5
ManualWorks requests the user profile from the authorization server with the token it received.
ManualWorks calls the authorization server directly, not through the web browser.
It might seem simpler to request the user profile directly instead of a token. However, OAuth is a framework for accessing many services and resources, not just for authentication. For example, it can be used to request address books or photo lists. That is why getting a token and requesting a service (the user profile) are separate steps.
Setting the OAuth URLs in ManualWorks
Set the URLs of the authorization server that authenticates users in ManualWorks.
To log in with OAuth 2.0, you must set the base URL in General Settings. Otherwise, login fails with the error “The base URL is not configured.”
The
redirect_urithat ManualWorks sends to the authorization server is the base URL followed by/r/oauth/auth.Users must access ManualWorks with the same host as the base URL, because after login they return to the host of the base URL.
1
Go to the <Admin | Preferences | API> menu, and click the <1Change> link in the “Authentication” panel.
2
Enter all the OAuth server URLs and save.
Enter the actual OAuth authentication addresses. For network security, we recommend HTTPS.
Enter the final address that does not redirect. For example, if https://www.example.com redirects to https://example.com, enter the address without www. ManualWorks calls the token URL with POST, and a redirect drops the request body, so the authorization code is not delivered. Starting with 6.0.26, an error that starts with "OAuth token endpoint redirected to" is written to the log in this case.
Implementing the Authorization Server
Implement the functions of the OAuth URLs you set in ManualWorks. There are three URLs to implement.
Add these functions to an existing website (application) that can access your employee or customer database.
- OAuth Server URL
The URL that authenticates users. If the user is not logged in, it sends the user to the login page. When authentication succeeds, it passes the authorization code to ManualWorks through a web browser redirect.
- OAuth Server Token URL
The URL where ManualWorks exchanges the authorization code for an access token. ManualWorks calls the authorization server directly, not through the web browser.
- OAuth Server User Profile URL
The URL where ManualWorks gets the user information with the access token. ManualWorks calls the authorization server directly, not through the web browser.
Implementing the OAuth Server URL
ManualWorks calls the authorization server through a web browser redirect with the following parameters.
redirect_uriThe ManualWorks address to redirect the web browser to after successful authentication. It is the base URL followed by
/r/oauth/auth.stateA safeguard against duplicate calls. It is sent back as a query string parameter after successful authentication.
response_typeAlways
code.client_idAn empty value.
scopeemailand the OAuth Server User Profile URL joined with a space.
When the user logs in to the authorization server successfully, redirect to redirect_uri with the following in the query string.
codeThe code string. There is no required format.
stateThe state value received from ManualWorks, passed back as is.
Note the following when you implement it.
For security, you can also redirect to a predefined address (http://127.0.0.1:1975/r/oauth/auth).
The
statevalue helps prevent security problems caused by duplicate calls and code exposure. We recommend filtering out duplicate requests with the samestatevalue when you implement the OAuth server.
Implementing the OAuth Server Token URL
ManualWorks sends a POST request to the authorization server with the following parameters in the request body in form format (application/x-www-form-urlencoded).
The token URL must respond directly without redirecting. A redirect changes the POST request to GET and drops the body, so the authorization code is not received.
codeThe code value received in the previous step.
client_idAn empty value.
client_secretAn empty value.
redirect_uriThe same address as in the authorization request.
grant_typeAlways
authorization_code.
The authorization server must return the result in JSON format.
Content-Type: application/json; charset=UTF-8
The JSON format is as follows.
{
"access_token": "access token",
"expires_in": 60
}expires_in is the validity period of the access token in seconds.
Implementing the OAuth Server User Profile URL
ManualWorks sends a GET request to the authorization server with the following parameter in the query string.
access_tokenThe token value received in the previous step.
The authorization server must return the user information that matches the token to ManualWorks in the following format.
Content-Type: application/json; charset=UTF-8
Return the following.
{
"id": "username",
"name": "user's name",
"email": "user's email address",
"locale": "ko",
"roles": "user's roles",
"groups": "groups the user belongs to"
}You can set locale to en or ko.
You can set several roles separated by semicolons. The available roles are as follows.
Code | Role |
|---|---|
admin | Administrator |
editor | Create Style |
writer | Write & Read |
reader | Read Only |
translator | Translator |
You can set several roles separated by semicolons.
"roles": "admin;writer"
If you set writer, you do not need to set reader.
To manage roles in ManualWorks instead, set roles to 3rabbitz. Sending an empty roles value has the same effect, and the roles of existing users stay unchanged.
"roles": "3rabbitz"
In this case, the role given to new users is determined by the DEFAULT_ROLE setting in Changing the Initial Role. If it is not set, new users get the Write & Read role.
You can set several group identifications in groups, separated by semicolons. For group identifications, see Managing Groups.
"groups": "marketing;support"
To manage groups in ManualWorks instead, set groups to 3rabbitz.
"groups": "3rabbitz"
Synchronizing Group Names
If the name of a group received through OAuth changes, you can also change the group name in ManualWorks by running the following command in the Console.
set-preference -name SUPPORT_OAUTH_GROUP_SYNC -value true
The groups property is then handled as an array of objects instead of a string.
{
"id": "username",
"name": "user's name",
"email": "user's email address",
"locale": "ko",
"roles": "admin;editor;writer",
"groups": [
{"id": "group_a", "name": "Group A"},
{"id": "group_b", "name": "Group B"}
]
}If there is no name property, the id property is used as the group name.
JSP Implementation Example
How you implement the authorization server depends on your programming language and development environment. The following example in Java and JSP can serve as a reference.
OAuthQueue.java
A Java class that links codes, tokens, and usernames.
package com.threerabbitz.oauth;
import java.util.LinkedList;
import java.util.UUID;
public class OAuthQueue {
private static final int MAX_SIZE = 10000;
private static OAuthQueue instance = new OAuthQueue();
public static OAuthQueue getInstance() {
return instance;
}
private LinkedList<OAuthInfo> queue = new LinkedList<OAuthInfo>();
public synchronized String getCode(String user) {
if (user == null) {
throw new IllegalArgumentException("user_cannot_be_null");
}
OAuthInfo result = new OAuthInfo(user);
queue.add(result);
if (queue.size() > MAX_SIZE) {
queue.removeFirst();
}
return result.code;
}
public synchronized String getToken(String code) {
if (code == null) {
throw new IllegalArgumentException("code_cannot_be_null");
}
for (int i = queue.size() - 1; i > -1; i--) {
OAuthInfo info = queue.get(i);
if (info.code.equals(code)) {
return info.token;
}
}
return null;
}
public synchronized String getUser(String token) {
if (token == null) {
throw new IllegalArgumentException("token_cannot_be_null");
}
for (int i = queue.size() - 1; i > -1; i--) {
OAuthInfo info = queue.get(i);
if (info.token.equals(token)) {
return info.user;
}
}
return null;
}
static private class OAuthInfo {
String code = UUID.randomUUID().toString();
String token = UUID.randomUUID().toString();
String user;
OAuthInfo(String user) {
this.user = user;
}
}
}8Limits the authentication data cache to 10,000 entries.
12Applies the singleton pattern.
56Declares the OAuthInfo class that holds the code, token, and username.
57Creates the code and token values with the java.util.UUID class.
oauth.jsp
A JSP file that checks whether the user is logged in and passes the code to ManualWorks through a web browser redirect.
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.threerabbitz.base.domain.User" %>
<%@ page import="com.threerabbitz.oauth.OAuthQueue" %>
<%
String redirectUri = request.getParameter("redirect_uri");
String state = request.getParameter("state");
User user = (User) session.getAttribute("user");
if (user != null) {
String code = OAuthQueue.getInstance().getCode(user.getUserid());
response.sendRedirect(redirectUri + "?code=" + code + "&state=" + state);
} else {
String path = "/oauth.jsp?redirect_uri=" + redirectUri + "&state=" + state;
session.setAttribute("path", path);
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
%>10Checks whether the user is logged in. Adjust it to your environment.
12Redirects with code and state in the query string.
15Sets the page to go to after login. Adjust it to your environment.
16Goes to the login page. Adjust it to your environment.
oauth_token.jsp
A JSP file that receives a code and returns a token.
<%@ page contentType="application/json; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.threerabbitz.oauth.OAuthQueue" %>
<%
String code = request.getParameter("code");
String token = OAuthQueue.getInstance().getToken(code);
if (token != null) {
out.print("{");
out.print("\"access_token\": \"" + token + "\",");
out.print("\"expires_in\": 60");
out.print("}");
}
%>1The Content-Type is application/json; charset=UTF-8.
11Sets the validity period with the expires_in property, in seconds.
oauth_user_profile.jsp
A JSP file that receives a token (access_token) and returns the user information.
<%@ page contentType="application/json; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="com.threerabbitz.base.domain.User" %>
<%@ page import="com.threerabbitz.base.domain.Users" %>
<%@ page import="com.threerabbitz.oauth.OAuthQueue" %>
<%
String token = request.getParameter("access_token");
User user = Users.find(OAuthQueue.getInstance().getUser(token));
if (user != null) {
out.print("{");
out.print("\"id\": \"" + user.getUserid() + "\",");
out.print("\"name\": \"" + user.getName() + "\",");
out.print("\"email\": \"" + user.getEmail() + "\",");
out.print("\"roles\": \"3rabbitz\",");
out.print("\"groups\": \"3rabbitz\"");
out.print("}");
}
%>1The Content-Type is application/json; charset=UTF-8.
9Finds the user information by username. Adjust it to your environment.
What to Know When You Use OAuth
Logging Out
Even after users log out of ManualWorks, they are logged in again automatically because they are still logged in to the OAuth server. To prevent this, set the path to go to after logout as follows.
Open the Console.
Enter the following command.
set-preference -name LOGOUT_REDIRECT_PATH -value https://www.3rabbitz.com
Set the URL to go to after logout with this option. Usually, this URL handles logout from the OAuth server.
Other Notes
Users created in ManualWorks must log in at http://127.0.0.1:1975/r/signon/login, not through OAuth.
When a user logs in with OAuth for the first time, the user is created in ManualWorks automatically. If the license limit is exceeded, the user is created with only the Read Only role, regardless of the roles property.
Users who log in with OAuth cannot change their name, email address, or password in My Profile.