How to make an authorization and access token requests with PKCE - authentication

Can you point me to the right way and set of parameters to:
Make an authorization request with PKCE to my identity endpoint (https://.../login) in Postman
In the attachments there is the list of parameters that send.
as grant_type value I use --> authorization_code
Unfortunately I get "bad request", Invalid_grant in Postman
make the access token request. In this request I get Invalid request. I guess I miss the parameter refresh token but I don't know how to get/generate it:
I wrote the code of the Azure function to request the Access Token, unfortunately I get {"error":"invalid_request"} from the token endpoint.
Here is my code, can you tell me what I am doing wrong?
[FunctionName("GetAccessToken")]
public async Task<IActionResult> GetAccessToken(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
ILogger log)
{
try
{
log.LogInformation("C# HTTP trigger function ''GetAccessToken'' processed a request.");
string clientSecret = "some secret";
string accessToken = "";
RequestAccessToken rT = new RequestAccessToken();
rT.Code = req.Form["code"];
rT.RedirectUri = req.Form["redirect_uri"];
rT.GrantType = req.Form["grant_type"];
rT.ClientId = req.Form["client_id"];
rT.CodeVerifier = req.Form["code_verifier"];
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("https://<access_token_endpoint_base_uri>");
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/x-www-form-urlencoded"));//ACCEPT header
var body = new { EntityState = new {
code = rT.Code,
redirect_uri = rT.RedirectUri,
grant_type = rT.GrantType,
client_id = rT.ClientId,
client_secret = clientSecret,
code_verifier = rT.CodeVerifier
} };
var result = await client.PostAsJsonAsync(
"/login",
body);
accessToken = await result.Content.ReadAsStringAsync();
}
return new OkObjectResult(accessToken);
}
catch (Exception ex)
{
log.LogInformation(ex.ToString());
return new ObjectResult(ex.ToString()) { StatusCode = 500 };
}
}

Steps 4 and 8 of my blog post show the standard PKCE parameters.
It is tricky to reproduce the whole flow via a tool such as Postman though, since there is typically also the need to follow redirects and manage a form post of user + password.

Related

How to use OAuth Authorization Code for CLIs

Trying to allow a CLI I'm developing to "login" via web browser and obtain an access token for the user's account, similar to how gcloud and github's CLIs do it. I realize it'll be using the OAuth Authorization Code flow.
But what about the client_secret?
I've found out that github cli just doesn't care about exposing it, and it's right there in the source code: https://github.com/cli/cli/blob/6a8deb1f5a9f2aa0ace2eb154523f3b9f23a05ae/internal/authflow/flow.go#L25-L26
Why is this not a problem? or is it?
I'm not yet using OAuth for the cli's login
STANDARDS
The CLI app is a native public client and should use authorization code flow + PKCE rather than a fixed client secret. It should also follow the flow from
RFC8252 and receive the browser response using a local HTTP (loopback) URI.
THIS IMPLEMENTATION
Looks like the github code here uses a client secret and does not use PKCE. You may have to provide a client secret if using this library, but it cannot be kept secret from users. Any user could easily view it, eg with an HTTP proxy tool.
CODE
If the infrastructure enables you to follow the standards, aim for something similar to this Node.js code.
* The OAuth flow for a console app
*/
export async function login(): Promise<string> {
// Set up the authorization request
const codeVerifier = generateRandomString();
const codeChallenge = generateHash(codeVerifier);
const state = generateRandomString();
const authorizationUrl = buildAuthorizationUrl(state, codeChallenge);
return new Promise<string>((resolve, reject) => {
let server: Http.Server | null = null;
const callback = async (request: Http.IncomingMessage, response: Http.ServerResponse) => {
if (server != null) {
// Complete the incoming HTTP request when a login response is received
response.write('Login completed for the console client ...');
response.end();
server.close();
server = null;
try {
// Swap the code for tokens
const accessToken = await redeemCodeForAccessToken(request.url!, state, codeVerifier);
resolve(accessToken);
} catch (e: any) {
reject(e);
}
}
}
// Start an HTTP server and listen for the authorization response on a loopback URL, according to RFC8252
server = Http.createServer(callback);
server.listen(loopbackPort);
// Open the system browser to begin authentication
Opener(authorizationUrl);
});
}
/*
* Build a code flow URL for a native console app
*/
function buildAuthorizationUrl(state: string, codeChallenge: string): string {
let url = authorizationEndpoint;
url += `?client_id=${encodeURIComponent(clientId)}`;
url += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
url += '&response_type=code';
url += `&scope=${scope}`;
url += `&state=${encodeURIComponent(state)}`;
url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
url += '&code_challenge_method=S256';
return url;
}
/*
* Swap the code for tokens using PKCE and return the access token
*/
async function redeemCodeForAccessToken(responseUrl: string, requestState: string, codeVerifier: string): Promise<string> {
const [code, responseState] = getLoginResult(responseUrl);
if (responseState !== requestState) {
throw new Error('An invalid authorization response state was received');
}
let body = 'grant_type=authorization_code';
body += `&client_id=${encodeURIComponent(clientId)}`;
body += `&redirect_uri=${encodeURIComponent(redirectUri)}`;
body += `&code=${encodeURIComponent(code)}`;
body += `&code_verifier=${encodeURIComponent(codeVerifier)}`;
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body,
};
const response = await fetch(tokenEndpoint, options);
if (response.status >= 400) {
const details = await response.text();
throw new Error(`Problem encountered redeeming the code for tokens: ${response.status}, ${details}`);
}
const data = await response.json();
return data.access_token;
}

Find out Cookie in Flutter

I use Flutter to make a HTTP-Post to a website to login. If I only send the data to login, the response of the HTTP-Post shows, that I`m not logged in. I tried to find out the cookie the Website sends. I did it with the software Postman. If I add the same cookie, I got at Postman, the HTTP-Post with Flutter works and the response shows, that Im logged in. After a while, the value of the cookie switched and the HTTP-Post via Flutter doent work.
How can I get the information about the actual value of the cookie from the website?
The code looks like this:
Future initiate() async {
ByteData bytes = await rootBundle.load('assets/Certificates/test.crt');
SecurityContext clientContext = new SecurityContext()
..setTrustedCertificatesBytes(bytes.buffer.asUint8List());
var client = new HttpClient(context: clientContext);
IOClient ioClient = new IOClient(client);
var form = <String, String>{
'user':'testuser',
'pass':'testpass',
'logintype':'login',
'pid':'128',
'submit':'Anmelden',
'tx_felogin_pi1[noredirect]':'0'
};
print(form);
var ris = await ioClient.get("https://www.test.de/login");
print(ris.headers);
http.Response res = await ioClient.post("https://www.test.de/login",body:form, headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded", "Cookie": "fe_typo_user=12345"}, encoding: Encoding.getByName("utf-8"));
ioClient.close();
client.close();
print(res.body.length);
return res.body.contains("logout");
}
This is how I get cookie:
static Future<String> login(String username, String password) async {
var url = _session._getUrl('/auth/login');
var map = new Map<String, dynamic>();
map['username'] = username;
map['password'] = password;
final response = await http.post(url, body: map);
var jsonResponse = convert.jsonDecode(response.body);
var cookies = response.headers['set-cookie'];
if (cookies != null) {
var csrf = _session._getCSRF(cookies);
LocalStorage.instance.setString('CSRF', csrf);
print(csrf);
}
return jsonResponse['msg'];
}

Extension Grants - Invalid Grant Type Delegation - Identity Server 4 .NET Core 2.2

I am trying to figure out how to implement a delegation grant type in conjunction with client credentials, by following the tutorial from HERE, which is literally one page, since I have and API1 resource calling another API2 resource.
I've implemented the IExtensionGrantValidator and copied the code from the docs using the class name they provided, and added the client with grant type delegation. However, I am not sure where and how to call this method below, at first I was calling it from the client and tried passing the JWT I initially got to call API1 to the DelegateAsync method but I kept getting a bad request
In API 1 you can now construct the HTTP payload yourself, or use the IdentityModel helper library:
public async Task<TokenResponse> DelegateAsync(string userToken)
{
var payload = new
{
token = userToken
};
// create token client
var client = new TokenClient(disco.TokenEndpoint, "api1.client", "secret");
// send custom grant to token endpoint, return response
return await client.RequestCustomGrantAsync("delegation", "api2", payload);
}
So, I tried from API1 requesting a token in a method called GetAPI2Response which attempts to call a method in API2:
[HttpGet]
[Route("getapi2response")]
public async Task<string> GetApi2Response()
{
var client = new HttpClient();
var tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
Address = "http://localhost:5005/connect/token",
GrantType = "delegation",
ClientId = "api1_client",
ClientSecret = "74c4148e-70f4-4fd9-b444-03002b177937",
Parameters = { { "scope", "stateapi" } }
});
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("http://localhost:6050/api/values");
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
return content;
}
return "failed";
}
However, this returns when debugging an invalid grant type. Strangely, I noticed when running IDSRV the code in the IExtensionGrantValidator method does not get hit, until you click the link for the discovery docs then it appears as a grant type
I'm obviously doing something wrong since I am not including the aforementioned DelegateAsync method from the docs, as its not clear to me where it goes.
The docs seem to be a bit outdated. With the actual extension methods there must be something like:
var tokenResponse = await client.RequestTokenAsync(new TokenRequest
{
Address = "http://localhost:5005/connect/token",
GrantType = "delegation",
ClientId = "api1_client",
ClientSecret = "74c4148e-70f4-4fd9-b444-03002b177937",
Parameters = new Dictionary<string, string>{{ "token", userToken }, { "scope", "stateapi" } }
})
you already implemented it, but forgot to add the initial token. When you extract it from the GetApi2Response() it can become your DelegateAsync.
Then your client configuration in Identityserver has to contain the delegation GrantType for the api1_client. Also don't forget the registration:
services.AddIdentityServer().AddExtensionGrantValidator<YourIExtensionGrantValidatorImpl>()

Thinktecture IdentityServer v3 LogOut for Implicit flow

How do I get the id_token for the implicit token to pass in the id_token hint for logout for implicit flow or is there another way? I have the end point /connect/endsession?
id_token_hint=
Not sure how I get the id_token from the implict flow all I get is a access_token and expiration. Is there a setting in IdSvr?
There's three components to this.
First ensure you're requesting an id_token from Identity Server when you're configuring the OIDC authentication in your Startup.cs (as mentioned by #leastprivilege above):
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/",
...
ResponseType = "id_token token", //(Here's where we request id_token!)
Secondly, using the OIDC notifications & after the security token is validated you add the id_token to your user's claims:
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Constants.ClaimTypes.GivenName,
Constants.ClaimTypes.Role);
// get userinfo data
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/" + Constants.RoutePaths.Oidc.UserInfo),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
userInfo.Claims.ToList().ForEach(ui => nid.AddClaim(new Claim(ui.Item1, ui.Item2)));
// keep the id_token for logout (**This bit**)
nid.AddClaim(new Claim(Constants.TokenTypes.IdentityToken, n.ProtocolMessage.IdToken));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
},
Finally, on the redirect for signout (also a notification event) you add the id_token to the Protocol Message:
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst(Constants.TokenTypes.IdentityToken);
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
You'll also need to ensure you setup the PostLogoutRedirectUris on the client within Identity Server:
new Client
{
Enabled = true,
ClientName = "(MVC) Web App",
ClientId = "mvc",
Flow = Flows.Implicit,
PostLogoutRedirectUris = new List<string>
{
"https://localhost:44300/" //(** The client's Url**)
}
}
That will ensure you give the user an option to return to the authorised client when they log out :)
All of this is pretty much as per the MVC Sample at https://identityserver.github.io/Documentation/docsv2/overview/mvcGettingStarted.html
Bit more than you asked for but hopefully that helps anyone else who's trying to figure it out too :)
To get an id_token, you have to ask for it. Use response_type=id_token token
Have you tried this?
ASP.Net Identity Logout
It should create the id token hint automatically

Attempt to reuse auth token to connect xamarin app to azure fails

This initial login succeeds:
public static MobileServiceClient MOBILE = new MobileServiceClient("https://myapp.azure-mobile.net/",myApplicationKey);
MobileServiceAuthenticationProvider GOOGLEPROVIDER = MobileServiceAuthenticationProvider.Google;
private async Task Connect() {
var USER = await MOBILE.LoginAsync(this, GOOGLEPROVIDER);
var CACHE = new Dictionary<string, string> { { "token", USER.MobileServiceAuthenticationToken } };
var ACCOUNT = new Account(USER.UserId, CACHE);
var STORE = AccountStore.Create(this);
STORE.Save(ACCOUNT, "Google");
}
but then this attempt to reuse the token to reconnect without a login page fails:
public async Task Reconnect() {
var STORE = AccountStore.Create(this);
var token = STORE.FindAccountsForService("Google").ToArray()[0].Properties["token"];
// token seems ok
var jsonToken = new JObject();
jsonToken.Add("access_token", token);
var USER = await MOBILE.LoginAsync(MobileServiceAuthenticationProvider.Google, jsonToken); // BOOM!
}
... with the following message: "The POST Google login request must contain both code and id_token in the body of the request."
What I am getting wrong here?
The token you use in the code, viz.
var CACHE = new Dictionary { { "token",USER.MobileServiceAuthenticationToken } };
The MobileServiceAuthenticationToken above is a token specific to MobileServices and cannot be used in the LoginAsync method (LoginAsync method requires a Google OAuth token.)
Please see this Get User Info from Google Api Using Azure Mobile Services for Xamarin Android