<!--
  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
  ~ and other contributors as indicated by the @author tags.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~ http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<chapter id="direct-access-grants">
    <title>Direct Access Grants</title>
    <para>
        Keycloak allows you to make direct REST invocations to obtain an access token.
        (See <ulink url="http://tools.ietf.org/html/rfc6749#section-4.3">Resource Owner Password Credentials Grant</ulink>
        from OAuth 2.0 spec).  To use it you must also have
        registered a valid Client to use as the "client_id" for this grant request.
    </para>
    <warning>
        <para>
            It is highly recommended that you do not use Direct Access Grants to write your own login pages for your application.
            You will lose a lot of features that Keycloak has if you do this.  Specifically all the account management, remember me,
            lost password, account reset features of Keycloak.  Instead, if you want to tailor the look and feel of Keycloak login
            pages, you should create your own <link linkend="themes">theme</link>. There are also security implications
            to using Direct Access Grants compared to the redirect based flows as you are exposing plain text passwords
            to applications directly.
        </para>
        <para>
            It is even highly recommended that you use the browser to log in for native mobile applications!  Android
            and iPhone applications allow you to redirect to and from the browser.  You can use this to redirect the user
            from your native mobile app to the web browser to perform login, then the browser will redirect back to your
            native application.
        </para>
    </warning>


    <para>
        The REST URL to invoke on is <literal>/{keycloak-root}/realms/{realm-name}/protocol/openid-connect/token</literal>.
        Invoking on this URL is a POST request and requires you to post the username and credentials of the user you want
        an access token for.  You must also pass along the "client_id" of the client you are creating
        an access token for.  This "client_id" is the Client Id specified in admin console (not it's id from DB!).  Depending on
        whether your client is <link linkend='access-types'>"public" or "confidential"</link>, you may also have to pass along
        it's client secret as well. We support pluggable client authentication, so alternatively you can use other form of client credentials like signed JWT assertion.
        See <link linkend="client_authentication">Client Authentication</link> section for more details. Finally you need to pass "grant_type"
        parameter with value "password" .
    </para>
    <para>
        For public client's, the POST invocation requires form parameters that contain the username,
        credentials, and client_id of your application.  For example:
<programlisting><![CDATA[
    POST /auth/realms/demo/protocol/openid-connect/token
    Content-Type: application/x-www-form-urlencoded

    username=bburke&password=geheim&client_id=customer-portal&grant_type=password]]>
</programlisting>
        The response would be this <ulink url="http://tools.ietf.org/html/rfc6749#section-4.3.3">standard JSON document</ulink> from the OAuth 2.0 specification.
<programlisting><![CDATA[
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
    "access_token":"2YotnFZFEjr1zCsicMWpAA",
    "token_type":"bearer",
    "expires_in":3600,
    "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "id_token":"tGzv3JOkF0XG5Qx2TlKWIA",
    "session-state":"234234-234234-234234"
}]]>
</programlisting>
    </para>
    <para>
        For confidential client's, you must create a Basic Auth <literal>Authorization</literal>
        header that contains the client_id and client secret.  And pass in the form parameters for username and for
        each user credential.  For example:
<programlisting><![CDATA[
    POST /auth/realms/demo/protocol/openid-connect/token
    Authorization: Basic atasdf023l2312023
    Content-Type: application/x-www-form-urlencoded

    username=bburke&password=geheim&grant_type=password]]>
</programlisting>

    </para>
    <para>As mentioned above, we support also other means of authenticating clients. In adition to default client_id and client secret,
        we also have signed JWT assertion by default. There is possibility to use any other form of client authentication implemented by you. See <link linkend="client_authentication">Client Authentication</link>
        section for more details.
    </para>
    <para>
        Here's a Java example using Apache HTTP Client and some Keycloak utility classes.:
<programlisting><![CDATA[
HttpClient client = new HttpClientBuilder()
                .disableTrustManager().build();


try {
   HttpPost post = new HttpPost(
           KeycloakUriBuilder.fromUri("http://localhost:8080/auth")
           .path(ServiceUrlConstants.TOKEN_PATH).build("demo"));
   List <NameValuePair> formparams = new ArrayList <NameValuePair>();
   formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "password"));
   formparams.add(new BasicNameValuePair("username", "bburke"));
   formparams.add(new BasicNameValuePair("password", "password"));

   if (isPublic()) { // if client is public access type
       formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
   } else {
       String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
       post.setHeader("Authorization", authorization);
   }
   UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
   post.setEntity(form);

   HttpResponse response = client.execute(post);
   int status = response.getStatusLine().getStatusCode();
   HttpEntity entity = response.getEntity();
   if (status != 200) {
      throw new IOException("Bad status: " + status);
   }
   if (entity == null) {
      throw new IOException("No Entity");
   }
   InputStream is = entity.getContent();
   try {
      AccessTokenResponse tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
   } finally {
      try {
          is.close();
      } catch (IOException ignored) { }
      }
} finally {
   client.getConnectionManager().shutdown();
}
]]>

</programlisting>
    </para>
    <para>
        Once you have the access token string, you can use it in REST HTTP bearer token authorized requests, i.e
<programlisting>
GET /my/rest/api
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
</programlisting>
    </para>
    <para>
        To logout you must use the refresh token contained in the AccessTokenResponse object.
    </para>
<programlisting>
    <![CDATA[
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
if (isPublic()) { // if client is public access type
    formparams.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, "customer-portal"));
} else {
    String authorization = BasicAuthHelper.createHeader("customer-portal", "secret-secret-secret");
    post.setHeader("Authorization", authorization);
}
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, tokenResponse.getRefreshToken()));
HttpResponse response = null;
URI logoutUri = KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth")
                    .path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
                    .build("demo");
HttpPost post = new HttpPost(logoutUri);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 204) {
   error(status, entity);
}
if (entity == null) {
   return;
}
InputStream is = entity.getContent();
if (is != null) is.close();
]]></programlisting>
</chapter>