HTTP Authentication

Old documentation (Jena 3.1.1 to Jena 4.2.0)

Jena 4.3.0 and later uses the JDK java.net.http package. Jena adds API support for challenge-based authentication and also provide HTTP digest authentication.

Authentication

There are 5 variations:

  1. Basic authentication
  2. Challenge-Basic authentication
  3. Challenge-Digest authentication
  4. URL user (that is, user@host.net in the URL)
  5. URL user and password in the URL (that is, user:password@host.net in the URL)

Basic authentication occurs where the app provides the user and password information to the JDK HttpClient and that information is always used when sending HTTP requests with that HttpClient. It does not require an initial request-challenge-resend to initiate. This is provided natively by the java.net.http JDK code. See HttpClient.newBuilder().authenticate(...).

Challenge based authentication, for “basic” or “digest”, are provided by Jena. The challenge happens on the first contact with the remote endpoint and the server returns a 401 response with an HTTP header saying which style of authentication is required. There is a registry of users name and password for endpoints which is consulted and the appropriate Authorization: header is generated then the request resent. If no registration matches, the 401 is passed back to the application as an exception.

Because it is a challenge response to a request, the request must be sent twice, first to trigger the challenge and then again with the HTTP authentication information. To make this automatic, the first request must not be a streaming request (the stream is not repeatable). All HTTP request generated by Jena are repeatable.

The URL can contain a userinfo part, either the users@host form, or the user:password@host form. If just the user is given, the authentication environment is consulted for registered users-password information. If user and password is given, the details as given are used. This latter form is not recommended and should only be used if necessary because the password is in-clear in the SPARQL query.

Jena also has support for bearer authentication.

JDK HttpClient.authenticator

    // Basic or Digest - determined when the challenge happens.
    AuthEnv.get().registerUsernamePassword(URI.create(dataURL), "user", "password");
    try ( QueryExecution qExec = QueryExecutionHTTP.service(dataURL)
            .endpoint(dataURL)
            .queryString("ASK{}")
            .build()) {
        qExec.execAsk();
    }

alternatively, the java platform provides basic authentication. This is not challenge based - any request sent using a HttpClient configured with an authenticator will include the authentication details. (Caution - including sending username/password to the wrong site!). Digest authentication must use AuthEnv.get().registerUsernamePassword.

    Authenticator authenticator = AuthLib.authenticator("user", "password");
    HttpClient httpClient = HttpClient.newBuilder()
            .authenticator(authenticator)
            .build();
    // Use with RDFConnection      
    try ( RDFConnection conn = RDFConnectionRemote.service(dataURL)
            .httpClient(httpClient)
            .build()) {
        conn.queryAsk("ASK{}");
    }
    try ( QueryExecution qExec = QueryExecutionHTTP.service(dataURL)
            .httpClient(httpClient)
            .endpoint(dataURL)
            .queryString("ASK{}")
            .build()) {
        qExec.execAsk();
    }

Challenge registration

AuthEnv maintains a registry of credentials and also a registry of which service URLs the credentials should be used. It supports registration of endpoint prefixes so that one registration will apply to all URLs starting with a common root.

The main function is AuthEnv.get().registerUsernamePassword.

   // Application setup code 
   AuthEnv.get().registerUsernamePassword("username", "password");
   ...
   try ( QueryExecution qExec = QueryExecutionHTTP.service(dataURL)
        .endpoint(dataURL)
        .queryString("ASK{}")
        .build()) {
       qExec.execAsk();
   }

When an HTTP 401 response with an WWW-Authenticate header is received, the Jena http handling code will will look for a suitable authentication registration (exact or longest prefix), and retry the request. If it succeeds, a modifier is installed so all subsequent request to the same endpoint will have the authentication header added and there is no challenge round-trip.

SERVICE

The same mechanism is used for the URL in a SPARQL SERVICE clause. If there is a 401 challenge, the registry is consulted and authentication applied.

In addition, if the SERVICE URL has a username as the userinfo (that is, https://users@some.host/...), that user name is used to look in the authentication registry.

If the userinfo is of the form “username:password” then the information as given in the URL is used.

    AuthEnv.get().registerUsernamePassword(URI.create("http://host/sparql"), "u", "p");
     // Registration applies to SERVICE.
    Query query = QueryFactory.create("SELECT * { SERVICE <http://host/sparql> { ?s ?p ?o } }");
    try ( QueryExecution qExec = QueryExecution.create().query(query).dataset(...).build() ) {
        System.out.println("Call using SERVICE...");
        ResultSet rs = qExec.execSelect();
        ResultSetFormatter.out(rs);
    }

Authentication Examples

jena-examples:arq/examples/auth/.

Bearer Authentication

Bearer authentication requires that the application to obtain a token to present to the server.

How this token is obtained depends on the deployment environment.

The application can either register the token to be used:

    AuthEnv.get().addBearerToken(targetURL, jwtString);

or can provide a token provider for 401 challeneges stating bearer authentication.

    AuthEnv.get().setBearerTokenProvider( 
        (uri, challenge)->{ ... ; return jwtString; });