Quão simples é uma conexão basica de um cliente OpenID ? (C#)
John Bradley acaba de postar uma ótimo artigo demonstrando como a vida pode ser simples quando se trata de OpenID Connect. Recomendo a leitur.
O OpenID Connect fornece muitas facilidades avançadas para atender a muitos recursos adicionais solicitados pela comunidade membro. É cheio de recursos que vão além da Autenticação Básica. No entanto, isso não significa que não possa ser usado para o caso simples de “Apenas Autenticação”.
O código de amostra no post de John está em PHP, então pensei em fornecer rapidamente as mesmas amostras em C#. aqui vamos nós.
Fazendo uma solicitação de Conexão OpenID
Para que o cliente faça uma solicitação openid connect, ele precisa ter as seguintes informações sobre o servidor:
- client identifier – Um identificador exclusivo emitido ao cliente (RP) para se identificar ao servidor de autorização. (por exemplo, 3214244)
- client secret – Um segredo compartilhado estabelecido entre o servidor de autorização e o cliente usado para assinar solicitações.
- endpoint de autorização do usuário final – O endpoint HTTP do servidor de autorização capaz de autenticar o usuário final e obter autorização. (por exemplo, https://server.example.com/authorize)
- endpoint de token – O endpoint HTTP do servidor de autorização capaz de emitir tokens de acesso.
Nos casos mais simples, essas informações são obtidas pelo desenvolvedor cliente, tendo lido a documentação do servidor e pré-cadastrado seu aplicativo. Em seguida, para uma solicitação de autenticação de osso de urso, você colocaria um link como este na página HTML:
<a href="https://server.example.com/authorize?grant_type=code&scope=openid&client_id=3214244&state=af1Ef">Login with Example.com</a>
O usuário inicia o login clicando no link “Login com Example.com” e é levado para o servidor onde lhe é solicitado nome de usuário/senha etc. se ela ainda não está logado example.com. Uma vez que ela concorda em fazer login no RP, o navegador é redirecionado de volta para a URL de chamada de volta no RP pelo redirecionamento 302. O código lateral do PHP Server pode parecer:
Response.Redirect(“https://client.example.com/cb?code=8rFowidZfjt&state=af1Ef”,true);
Nota: estado é o parâmetro que é usado para proteger contra XSRF. Ele vincula a solicitação à sessão do navegador. É recomendado, mas não obrigatório em OAuth e foi omitido para tornar o exemplo estático. Isso deve ser simples o suficiente?
Chamando o endpoint do Token para obter id_token
Agora que o RP tem o ‘código’, você precisa obter o id_token do endpoint do token. O id_token é a afirmação de informações de login do usuário. O que você faz? Basta obtê-lo com HTTP Basic Auth usando client_id, client_secret, e o código que você tem no primeiro passo. Usando C#, pareceria:
var code = Request.Form[”code”];
var client = new WebClient();
NetworkCredential credentials = new NetworkCredential("testuser", "testpass");
client.Credentials = credentials;
client.Headers.Add("Content-Type","application/json; charset=utf-8");
var responseJson = client.DownloadString(new Uri("https://server.example.com/token?code="+code));
O resultado, respostaJson, conterá um JSON como este (envoltórios de linha apenas para fins de exibição):
{
"access_token": "SlAV32hkKG",
"token_type": "Bearer",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJodHRwczovL3NlcnZlci5leGFtcGxlLmNvbSIsInVzZXJfaWQiOiIyND.
gyODk3NjEwMDEiLCJhdWQiOiJodHRwOi8vY2iwiZXhwIjoxxpZW50LmV4YW1wbGUuY29tIMzExMjgxOTcwfSA.
eDesUD0vzDH3T1G3liaTNOrfaeWYjuRCEPNXVtaazNQ”
}
Para uma simples autenticação, ignoraremos “access_token”, “token_type” etc. O que você só se importa é com o “id_token”. “id_token” é codificado em um formato chamado JSON Web Token (JWT). JWT é a concatenação de “cabeçalho”, “corpo”, “assinatura” por períodos (.). Uma vez que você está recebendo isso diretamente através do canal protegido TLS que está verificando a identidade do certificado do servidor, você não precisa verificar a assinatura para integridade, então você apenas tirar a segunda parte dele e base64url_decode-lo para obter as informações do id_token. Então, em c# você pode fazer algo como:
JObject o = JObject.Parse(response);
var token = o.id_token;
var id_array = token.split('.');
var id_body = Convert.FromBase64String(id_array[1]);
A afirmação resultante, id_body no exemplo acima, sobre o usuário (após uma formatação bastante) é:
{
"iss": https://server.example.com,
"user_id": "248289761001",
"aud": "3214244",
"iat": 1311195570,
"exp": 1311281970
}
“iss” está mostrando o emissor deste token, neste caso, o server.example.com. O emissor deve corresponder ao emissor esperado para o ponto final do token, se for diferente, você deve rejeitar o token. o “iss” é o nome espaço do user_id, que é único dentro do emissor e nunca reatribuído. Quando o cliente armazena o identificador do usuário, ele deve armazenar a tupla do user_id e iss.
“aud” significa “público” e mostra quem é o público deste token. Neste caso, é o client_id do RP. Se for diferente, você deve rejeitar o token.
“iat” significa o tempo em que o token foi emitido. Isso pode ser ignorado neste fluxo, pois o cliente está falando diretamente com o ponto final do token.
“exp” é o tempo de validade do token. Se o tempo atual for depois de “exp”, por exemplo, em PHP, se $exp < tempo(); o RP deve rejeitar o token também. Então, é isso. Agora você sabe quem é o usuário, ou seja, você autenticou o usuário. Tudo isso na forma de código seria:
private bool checkID(idBody, issuer, clientID){
JObject o = JObject.Parse(idBody);
if (o.iss != issuer)
return false;
if (o.aud != clientID)
return false;
if (o.exp < DateTime.UtcNow)
return false;
return true;
}
— Mais uma vez, não deixe de ler o post de John http://www.thread-safe.com/2012/07/how-simple-is-openid-connect-basic.html
Autor: DAVI