An external endpoint must support three modes for HTTP GET requests: checkid_setup, check_authentication, and checkid_immediate. The mode is indicated in the query string parameter "openid.mode".

Checkid Setup

The checkid_setup mode is called from the user agent Web browser. The endpoint should redirect the user to an login screen, if needed, and provide their name, email, and a unique identifier through redirecting the user to the return_to URL provided in the query string parameter "openid.return_to".


if ("checkid_setup".equals(request.getParameter("openid.mode"))) {
    // called by user agent to be redirected to login screen (as needed)
    String url = request.getRequestURL().toString();
    String return_to = request.getParameter("openid.return_to");
    StringBuilder sb = new StringBuilder("LoginServlet");
    sb.append("?return_to=").append(URLEncoder.encode(return_to));
    sb.append("&op_endpoint=").append(URLEncoder.encode(url));
    response.setStatus(303);
    response.setHeader("Location", sb.toString());
}

To have a J2EE application prompt the user agent to login, put a Servlet in a security-contraint. Shown here is LoginServlet protected by a realm in WEB-INF/web.xml.


<security-constraint>
  <display-name>Make sure all users have logged in before accessing the LoginServlet</display-name>
  <web-resource-collection>
     <web-resource-name>Protected Area</web-resource-name>
     <url-pattern>/servlets/servlet/LoginServlet</url-pattern>
  </web-resource-collection>
  <auth-constraint>
     <!-- Anyone with one of the listed roles may access this area -->
     <role-name>tomcat</role-name>
     <role-name>role1</role-name>
  </auth-constraint>
</security-constraint>

Once the user agent has authenticated/logged in, their name, email, and a persistent unique identifier must be sent to the return_to URL. The format of the response must conform to OpenID Authentication Response. The fullname and email must conform to OpenID Attribute Exchange Fetch Response Format.


// called by user agent to be redirected back after logging in
String return_to = request.getParameter("return_to");
String op_endpoint = request.getParameter("op_endpoint");
StringBuilder sb = new StringBuilder(return_to);
if (return_to.indexOf('?') > 0) {
    sb.append('&');
} else {
    sb.append('?');
}
sb.append("openid.ns=http://specs.openid.net/auth/2.0");
if (this.isAuthorized(request)) {
    String id = (String) request.getSession(true).getAttribute("id");
    if (id == null) {
        id = request.getSession(true).getId();
        request.getSession(true).setAttribute("id", id);
    }
    String user_fullname = this.getUserFullName(request);
    String user_email = this.getUserEmail(request);
    String user_uri = this.getUserIdentifier(request);
    DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    String now = df.format(new Date());
    sb.append("&openid.mode=id_res");
    sb.append("&openid.op_endpoint=").append(URLEncoder.encode(op_endpoint));
    sb.append("&openid.claimed_id=").append(URLEncoder.encode(user_uri));
    sb.append("&openid.identity=").append(URLEncoder.encode(user_uri));
    sb.append("&openid.return_to=").append(URLEncoder.encode(return_to));
    sb.append("&openid.ns.ax=http://openid.net/srv/ax/1.0");
    sb.append("&openid.ax.mode=fetch_response");
    sb.append("&openid.ax.type.email=http://axschema.org/contact/email");
    sb.append("&openid.ax.type.fullname=http://axschema.org/namePerson");
    sb.append("&openid.ax.value.email=").append(URLEncoder.encode(user_email));
    sb.append("&openid.ax.value.fullname=").append(URLEncoder.encode(user_fullname));
    sb.append("&openid.response_nonce=").append(now).append(URLEncoder.encode(id));
} else {
    sb.append("&openid.mode=cancel");
}
response.setStatus(303);
response.setHeader("Location", sb.toString());

In the above example the user agent session ID is encoded in the openid.response_nonce parameter that is sent to the return_to URL, this will later be used to verify the information is valid.

Check Authentication

Once the user agent has been authenticated, the system must verify the information directly to the external endpoint. In order for a Servlet endpoint to access the necessary HttpSession information, an HttpSessionListener can be used to track the local user agent sessions. A listener must be registered in WEB-INF/web.xml.


<listener>
    <listener-class>SessionTracker</listener-class>
</listener>

public class SessionTracker implements HttpSessionListener {
  public void sessionCreated(HttpSessionEvent event) {
    HttpSession session = event.getSession();
    ServletContext ctx = session.getServletContext();
    Map<String, HttpSession> sessions = (Map) ctx.getAttribute("active-sessions");
    if (sessions == null) {
        synchronized (ctx) {
            sessions = (Map) ctx.getAttribute("active-sessions");
            if (sessions == null) {
                sessions = Collections.synchronizedMap(new HashMap<String, HttpSession>());
            }
            ctx.setAttribute("active-sessions", sessions);
        }
    }
    String id = (String) session.getAttribute("id");
    if (id == null) {
        id = session.getId();
        session.setAttribute("id", id);
    }
    sessions.put(id, session);
  }
  public void sessionDestroyed(HttpSessionEvent event) {
    HttpSession session = event.getSession();
    ServletContext ctx = session.getServletContext();
    Map<String, HttpSession> sessions = (Map) ctx.getAttribute("active-sessions");
    if (sessions != null) {
        sessions.remove(session.getId());
    }
  }    
}

The system will sent a GET request to the external endpoint and pass all the parameters that it was sent for verification. The endpoint must verify that all of the parameters are correct and none of them have been modified in transit. The response must comply with Verifying Directly with the OpenID Provider.


if ("check_authentication".equals(request.getParameter("openid.mode"))) {
    // called by client for session validation
    String user_fullname = request.getParameter("openid.ax.value.fullname");
    String user_email = request.getParameter("openid.ax.value.email");
    String user_uri = request.getParameter("openid.identity");
    String response_nonce = request.getParameter("openid.response_nonce");
    String sessionId = response_nonce.substring(response_nonce.indexOf('Z') + 1);
    ServletContext ctx = request.getServletContext();
    Map<String, HttpSession> sessions = (Map) ctx.getAttribute("active-sessions");
    HttpSession session = sessions == null ? null : sessions.get(sessionId);

    response.setContentType("text/plain");
    PrintWriter out = response.getWriter();
    out.print("ns:http://specs.openid.net/auth/2.0\n");
    if (session != null
            && user_fullname.equals(this.getUserFullName(session))
            && user_email.equals(this.getUserEmail(session))
            && user_uri.equals(this.getUserIdentifier(session))) {
        out.print("is_valid:true\n");
    } else {
        out.print("is_valid:false\n");
    }
}

Checkid Immediate

If the validation fails the system will redirect their user agent to the endpoint with the mode checkid_immediate. This is similar to checkid_setup, but indicates the endpoint should redirect the user directly back to the system without any user prompts. If the user is not logged in, the response should contain the mode setup_needed to indicate the user must login before proceeding, otherwise a mode of id_res with updated credentials (as with checkid_setup) should be returned.


if ("checkid_immediate".equals(request.getParameter("openid.mode"))) {
    // called by user agent to be immediately redirected back
    String return_to = request.getParameter("openid.return_to");
    String op_endpoint = request.getRequestURL().toString();
    StringBuilder sb = new StringBuilder(return_to);
    if (return_to.indexOf('?') > 0) {
        sb.append('&');
    } else {
        sb.append('?');
    }
    sb.append("openid.ns=http://specs.openid.net/auth/2.0");
    if (this.isAuthorized(request)) {
        String id = request.getSession(true).getId();
        String user_fullname = this.getUserFullName(request);
        String user_email = this.getUserEmail(request);
        String user_uri = this.getUserIdentifier(request);
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        df.setTimeZone(TimeZone.getTimeZone("UTC"));
        String now = df.format(new Date());
        sb.append("&openid.mode=id_res");
        sb.append("&openid.op_endpoint=").append(URLEncoder.encode(op_endpoint));
        sb.append("&openid.claimed_id=").append(URLEncoder.encode(user_uri));
        sb.append("&openid.identity=").append(URLEncoder.encode(user_uri));
        sb.append("&openid.return_to=").append(URLEncoder.encode(return_to));
        sb.append("&openid.ns.ax=http://openid.net/srv/ax/1.0");
        sb.append("&openid.ax.mode=fetch_response");
        sb.append("&openid.ax.type.email=http://axschema.org/contact/email");
        sb.append("&openid.ax.type.fullname=http://axschema.org/namePerson");
        sb.append("&openid.ax.value.email=").append(URLEncoder.encode(user_email));
        sb.append("&openid.ax.value.fullname=").append(URLEncoder.encode(user_fullname));
        sb.append("&openid.response_nonce=").append(now).append(URLEncoder.encode(id));
    } else {
        sb.append("&openid.mode=setup_needed");
    }
    response.setStatus(303);
    response.setHeader("Location", sb.toString());
}