# Авторизация через .htaccess

У сервера `apache` есть возможность сделать базовую авторизацию. Чтобы закрыть директорию, в неё нужно поместить два файла – `.htaccess` и `.htpasswd`.  

## Содержание файла .htaccess

```bash
# .htaccess
AuthType Basic
AuthName "Authorization"
AuthUserFile /путь_до_директории/.htpasswd
Require valid-user
```

`AuthName "Authorization"` – сообщение в окне ввода логина и пароля, кириллица не поддерживается, в Google Chrome вообще не выводится.  
`AuthUserFile /путь_до_директории/.htpasswd` – путь до файла с паролями.


Авторизацией можно закрыть только определенные файлы, например `ZIP-архивы`.
```bash
# .htaccess
<Files arhive.zip>
    AuthType Basic
    AuthName "Authorization"
    AuthUserFile /путь_до_директории/.htpasswd
    Require valid-user
</Files>
```
На некоторых хостингах авторизация на статические файлы (изображения, шрифты и т.д.) может не работать т.к. они отдаются через `Nginx`.

Стоит проверить прямой доступ к самим файлам `.htaccess` и `.htpasswd` из браузера, если да, то закрыть его:

```bash
# .htaccess
<FilesMatch ".(htaccess|htpasswd)$">
    Order Allow,Deny
    Deny from all
</FilesMatch>
```

## Пароли в htpasswd

В файле хранится пары логина и хеша пароля, например:

```
admin:$apr1$TCrF2kqA$TSMYziwt.qCkrct9yx4vv1
```
Логин может содержать латинские буквы, цифры, - и _, регистрозависимый.

### Создание пароля в MD5
[Подробности](https://runebook.dev/ru/docs/apache_http_server/programs/htpasswd)
```
htpasswd -m .htpasswd user
```

### Bcrypt
В настоящее время считается очень безопасным, работает начиная с версии 2.4, формат:

`$2y$` или `$2a$` + результат алгоритма `crypt_blowfish`.

```php
<?php
function bcrypt($password)
{
	$rounds = 12;
	$salt = sprintf('$2a$%02d$', $rounds) . substr(str_replace('+', '.', base64_encode(pack('N4', mt_rand(), mt_rand(), mt_rand(), mt_rand()))), 0, 22);
	return crypt($password, $salt);
}
 
echo bcrypt('123456'); // $2a$12$dMHIiiPfeSMxqj3/Wt1.z.Mo7NPza1x/WANl7hDXZJzxxKKorz5um
```

### MD5 (APR)
Специфический алгоритм `Apache` (1000 итераций `MD5` случайной соли и пароля), работает во всех версиях.

`$apr1$` + результат алгоритма.

```php
<?php
function crypt_apr1_md5($password)
{
	$salt = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, 8);
	$len = strlen($password);
	$text = $password . '$apr1$' . $salt;
	$bin = pack('H32', md5($password . $salt . $password));
	for($i = $len; $i > 0; $i -= 16) {
		$text .= substr($bin, 0, min(16, $i));
	}
	for($i = $len; $i > 0; $i >>= 1) {
		$text .= ($i & 1) ? chr(0) : $password{0};
	}
	$bin = pack('H32', md5($text));
	for($i = 0; $i < 1000; $i++) {
		$new = ($i & 1) ? $password : $bin;
		if ($i % 3) {
			$new .= $salt;
		}
		if ($i % 7) {
			$new .= $password;
		}
		$new .= ($i & 1) ? $bin : $password;
		$bin = pack('H32', md5($new));
	}
   
	$tmp = '';
	for ($i = 0; $i < 5; $i++) {
		$k = $i + 6;
		$j = $i + 12;
		if ($j == 16) $j = 5;
		$tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
	}
	$tmp = chr(0) . chr(0) . $bin[11] . $tmp;
	$tmp = strtr(
		strrev(substr(base64_encode($tmp), 2)),
		'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
		'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
	);
 
	return '$apr1$' . $salt . '$' . $tmp;
}
 
echo crypt_apr1_md5('123456'); // $apr1$h9j4azoy$unmKNqjZlRfZv5xRetm9p1
```

### Crypt
Работает только на `Unix` хостингах и до версии `Apache 2.2.17`. Ограничивает длину пароля до 8 символов. Считается небезопасным.

```php
<?php
function crypt3($password)
{
	return crypt($password, substr($password, 0, 2));
}
 
echo crypt3('123456'); // 12tir.zIbWQ3c
PHP
SHA-1
Этот алгоритм небезопасен по современным стандартам, работает во всех версиях.

{SHA} + результат SHA-1 (бинарная строка из 20-ти символов) закодированный в Base64.

function hash_sha1($password)
{
	return '{SHA}' . base64_encode(sha1($password, true));
}
 
echo hash_sha1('123456'); // {SHA}fEqNCco3Yq9h5ZUglD3CZJT4lBs=
```

## Авторизация и выход

Реквизиты доступа к закрытой директории можно передать в URL:

```
https://логин:пароль@example.com/path
```

Если такие `URL` использовать в `src` изображений, скриптов и стилей, то работать они не будут, вызвав ошибку:

```Subresource requests whose URLs contain embedded credentials (e.g. `https://user:pass@host/`) are blocked.```

Завершение сеанса происходит по закрытию браузера, но не вкладки. 
### На стороне JS

```js
document.execCommand("ClearAuthenticationCache");
```
A "basic" way
```js
var p = window.location.protocol + '//'
// current location must return 200 OK for this GET
window.location = window.location.href.replace(p, p + 'logout:password@')
```

An "asynchronous" way of doing the above is to do an AJAX call utilizing the logout username.  
Example:
```js
(function(safeLocation){
    var outcome, u, m = "You should be logged out now.";
    // IE has a simple solution for it - API:
    try { outcome = document.execCommand("ClearAuthenticationCache") }catch(e){}
    // Other browsers need a larger solution - AJAX call with special user name - 'logout'.
    if (!outcome) {
        // Let's create an xmlhttp object
        outcome = (function(x){
            if (x) {
                // the reason we use "random" value for password is 
                // that browsers cache requests. changing
                // password effectively behaves like cache-busing.
                x.open("HEAD", safeLocation || location.href, true, "logout", (new Date()).getTime().toString())
                x.send("")
                // x.abort()
                return 1 // this is **speculative** "We are done." 
            } else {
                return
            }
        })(window.XMLHttpRequest ? new window.XMLHttpRequest() : ( window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : u ))
    }
    if (!outcome) {
        m = "Your browser is too old or too weird to support log out functionality. Close all windows and restart the browser."
    }
    alert(m)
    // return !!outcome
})(/*if present URI does not return 200 OK for GET, set some other 200 OK location here*/)
```
Other way

```js
 function logout(secUrl, redirUrl) {
    if (bowser.msie) {
        document.execCommand('ClearAuthenticationCache', 'false');
    } else if (bowser.gecko) {
        $.ajax({
            async: false,
            url: secUrl,
            type: 'GET',
            username: 'logout'
        });
    } else if (bowser.webkit) {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", secUrl, true);
        xmlhttp.setRequestHeader("Authorization", "Basic logout");
        xmlhttp.send();
    } else {
        alert("Logging out automatically is unsupported for " + bowser.name
            + "\nYou must close the browser to log out.");
    }
    setTimeout(function () {
        window.location.href = redirUrl;
    }, 200);
}
```