001package com.nimbusds.oauth2.sdk.auth; 002 003 004import java.io.UnsupportedEncodingException; 005import java.net.URLDecoder; 006import java.net.URLEncoder; 007import java.nio.charset.Charset; 008 009import net.jcip.annotations.Immutable; 010 011import com.nimbusds.jose.util.Base64; 012 013import com.nimbusds.oauth2.sdk.ParseException; 014import com.nimbusds.oauth2.sdk.id.ClientID; 015import com.nimbusds.oauth2.sdk.http.HTTPRequest; 016 017 018/** 019 * Client secret basic authentication at the Token endpoint. Implements 020 * {@link ClientAuthenticationMethod#CLIENT_SECRET_BASIC}. 021 * 022 * <p>Example HTTP Authorization header (for client identifier "s6BhdRkqt3" and 023 * secret "7Fjfp0ZBr1KtDRbnfVdmIw"): 024 * 025 * <pre> 026 * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 027 * </pre> 028 * 029 * <p>Related specifications: 030 * 031 * <p>Related specifications: 032 * 033 * <ul> 034 * <li>OAuth 2.0 (RFC 6749), sections 2.3.1 and 3.2.1. 035 * <li>OpenID Connect Core 1.0, section 9. 036 * <li>HTTP Authentication: Basic and Digest Access Authentication 037 * (RFC 2617). 038 * </ul> 039 */ 040@Immutable 041public final class ClientSecretBasic extends PlainClientSecret { 042 043 044 /** 045 * The default character set for the client ID and secret encoding. 046 */ 047 private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); 048 049 050 /** 051 * Creates a new client secret basic authentication. 052 * 053 * @param clientID The client identifier. Must not be {@code null}. 054 * @param secret The client secret. Must not be {@code null}. 055 */ 056 public ClientSecretBasic(final ClientID clientID, final Secret secret) { 057 058 super(ClientAuthenticationMethod.CLIENT_SECRET_BASIC, clientID, secret); 059 } 060 061 062 /** 063 * Returns the HTTP Authorization header representation of this client 064 * secret basic authentication. 065 * 066 * <p>Note that OAuth 2.0 (RFC 6749, section 2.3.1) requires the client 067 * ID and secret to be {@code application/x-www-form-urlencoded} before 068 * passing them to the HTTP basic authentication algorithm. This 069 * behaviour differs from the original HTTP Basic Authentication 070 * specification (RFC 2617). 071 * 072 * <p>Example HTTP Authorization header (for client identifier 073 * "Aladdin" and password "open sesame"): 074 * 075 * <pre> 076 * 077 * Authorization: Basic QWxhZGRpbjpvcGVuK3Nlc2FtZQ== 078 * </pre> 079 * 080 * <p>See RFC 2617, section 2. 081 * 082 * @return The HTTP Authorization header. 083 */ 084 public String toHTTPAuthorizationHeader() { 085 086 StringBuilder sb = new StringBuilder(); 087 088 try { 089 sb.append(URLEncoder.encode(getClientID().getValue(), UTF8_CHARSET.name())); 090 sb.append(':'); 091 sb.append(URLEncoder.encode(getClientSecret().getValue(), UTF8_CHARSET.name())); 092 093 } catch (UnsupportedEncodingException e) { 094 095 // UTF-8 should always be supported 096 } 097 098 return "Basic " + Base64.encode(sb.toString().getBytes(UTF8_CHARSET)); 099 } 100 101 102 @Override 103 public void applyTo(final HTTPRequest httpRequest) { 104 105 httpRequest.setAuthorization(toHTTPAuthorizationHeader()); 106 } 107 108 109 /** 110 * Parses a client secret basic authentication from the specified HTTP 111 * Authorization header. 112 * 113 * @param header The HTTP Authorization header to parse. Must not be 114 * {@code null}. 115 * 116 * @return The client secret basic authentication. 117 * 118 * @throws ParseException If the header couldn't be parsed to a client 119 * secret basic authentication. 120 */ 121 public static ClientSecretBasic parse(final String header) 122 throws ParseException { 123 124 String[] parts = header.split("\\s"); 125 126 if (parts.length != 2) 127 throw new ParseException("Unexpected number of HTTP Authorization header value parts: " + parts.length); 128 129 if (! parts[0].equalsIgnoreCase("Basic")) 130 throw new ParseException("HTTP authentication must be \"Basic\""); 131 132 String credentialsString = new String(new Base64(parts[1]).decode(), UTF8_CHARSET); 133 134 String[] credentials = credentialsString.split(":", 2); 135 136 if (credentials.length != 2) 137 throw new ParseException("Missing credentials delimiter \":\""); 138 139 try { 140 String decodedClientID = URLDecoder.decode(credentials[0], UTF8_CHARSET.name()); 141 String decodedSecret = URLDecoder.decode(credentials[1], UTF8_CHARSET.name()); 142 143 return new ClientSecretBasic(new ClientID(decodedClientID), new Secret(decodedSecret)); 144 145 } catch (UnsupportedEncodingException e) { 146 147 throw new ParseException(e.getMessage(), e); 148 } 149 } 150 151 152 /** 153 * Parses a client secret basic authentication from the specified HTTP 154 * request. 155 * 156 * @param httpRequest The HTTP request to parse. Must not be 157 * {@code null} and must contain a valid 158 * Authorization header. 159 * 160 * @return The client secret basic authentication. 161 * 162 * @throws ParseException If the HTTP Authorization header couldn't be 163 * parsed to a client secret basic 164 * authentication. 165 */ 166 public static ClientSecretBasic parse(final HTTPRequest httpRequest) 167 throws ParseException { 168 169 String header = httpRequest.getAuthorization(); 170 171 if (header == null) 172 throw new ParseException("Missing HTTP Authorization header"); 173 174 return parse(header); 175 } 176}